Compare commits

..

82 Commits

Author SHA1 Message Date
Alex The Bot
0aae9696f6 Version v1.91.1 2023-12-16 17:26:51 +00:00
martin
2f95cb89c1 fix(web): use env for web folder path (#5753)
* fix: use env for web folder path

* feat: use constant

* fix: use join

* update docs

* fix: icon
2023-12-16 11:15:30 -06:00
Mert
cb1201e690 chore(web): update job dashboard (#5745)
* rename clip encoding to smart search

* update job subtitles

* update api

* update smart search job title and subtitle

* fix `getJobName`

* change smart search icon

* formatting

* wording

* update reference to clip

* formatting

* update reference to Encode CLIP
2023-12-16 10:50:46 -06:00
Daniel Dietzler
a2deba4734 fix(web): never ungroup map markers. ever. (#5730) 2023-12-16 10:49:58 -06:00
Alex
ae2608e31d fix(web): cannot save exclusion pattern (#5738)
* fix(web): Ccannot save exclusive pattern

* remove console log
2023-12-16 10:48:49 -06:00
Mert
d8756f3897 fix(web): searching places (#5746) 2023-12-16 10:48:27 -06:00
Mohamed BOUSSAID
7839be3b49 Adding the new models to the whitelist (#5736) 2023-12-15 22:45:14 +00:00
Jason Rasmussen
94e11d52dc docs: fix redirects for cloudflare (#5734) 2023-12-15 15:20:50 -06:00
Jon Howell
05a1283500 fix(docs): Add title for External Library guide (#5732) 2023-12-15 14:38:14 -05:00
Alex
f8519d60c7 chore: post release tasks 2023-12-15 13:25:37 -06:00
Jon Howell
899c71f297 docs: add a walk-through guide for External Libraries (#5594)
* Add a walk-through guide for External Libraries

* Apply prettier to markdown

* Add screenshots for GUI elements

* fix format
2023-12-15 12:46:43 -06:00
martin
2aa5f55cbf docs: update milestone page (#5663) 2023-12-15 09:50:13 -06:00
Alex The Bot
e9a8daa924 Version v1.91.0 2023-12-15 15:22:37 +00:00
Alex
c2cda5f3b0 fix(web): map thumbnail stretch (#5721) 2023-12-15 09:17:28 -06:00
bo0tzz
e29b80845b chore: Update docs url config (#5716) 2023-12-15 08:57:36 -06:00
dependabot[bot]
6c3bfc6f0f chore(deps): bump actions/upload-artifact from 3 to 4 (#5719)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 08:56:47 -05:00
dependabot[bot]
9cc904fb2a chore(deps): bump actions/download-artifact from 3 to 4 (#5718)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 08:56:11 -05:00
Mert
3e54ee5052 feat(ml): add new models (#5710) 2023-12-14 23:34:41 -06:00
Mert
458257847e fix(server): disable classification by default (#5708)
* disable classification by default

* fixed tests
2023-12-14 23:34:18 -06:00
renovate[bot]
16f385626e chore(deps): update dependency sql-formatter to v15 (#5709)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-15 00:28:05 -05:00
renovate[bot]
e05c7f5b76 chore(deps): update mambaorg/micromamba:bookworm-slim docker digest to 5ea70d2 (#5707)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-14 23:09:48 -05:00
renovate[bot]
5c8eaa6859 chore(deps): update base-image to v20231214 (#5705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-14 21:22:06 -06:00
martin
4c5397d7e6 feat(web): add types to dispatcher (#5700)
* feat: add types to dispatcher

* fix: create album name

* pr feedback

* pr feedback

* pr feedback

* fix: api key name

* remove newSharedAlbum

* pr feedback

* fix: api key creation

* on:close

* fix: owner

* fix: onclose

* remove unused code

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-14 20:54:21 -06:00
martin
502495883d fix(web): log out (#5706)
* fix: logging out

* fix: websocket

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-15 02:29:05 +00:00
bo0tzz
2836b8cda9 fix: Catchall category for release notes (#5701) 2023-12-14 14:01:17 -06:00
bo0tzz
e4ee224e16 feat(ci): Automatic categories in generated release notes (#5684)
* feat(ci): Automatic categories in generated release notes

* ci: Enforce PR labels

* chore: Job name

* fix: Label names
2023-12-14 13:54:37 -06:00
Mert
d729c863c8 chore(ml): improve shutdown (#5689) 2023-12-14 13:51:24 -06:00
Jason Rasmussen
9768931275 feat(web,server)!: runtime log level (#5672)
* feat: change log level at runtime

* chore: open api

* chore: prefer env over runtime

* chore: remove default env value
2023-12-14 16:55:40 +00:00
martin
f2270ad757 fix(web): user management responsive design (#5698)
* fix: user management tailwind

* use top instead of inset-y-0

* add types to createEventDispatcher
2023-12-14 10:55:15 -06:00
Po-Ru, Lin
8e39d389b5 feat: lazy loading on album/sharing/search (#5696)
* feat(frontend): Lazy loading on album

* feat(frontend): Lazy loading on search & sharing

Issue #5418
2023-12-14 10:48:29 -06:00
Jason Rasmussen
9bb6befc92 docs: clean-up old references (#5697)
* docs: clean-up old references

* chore: fix ref
2023-12-14 09:53:08 -05:00
waclaw66
3a2e9b6298 feat(web): increase map max zoom (#5693)
* increate max zoom

* increase max zoom on mobile
2023-12-14 08:40:56 -05:00
dependabot[bot]
a4c057ba63 chore(deps): bump github/codeql-action from 2 to 3 (#5694)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-14 08:08:22 -05:00
Slavik
679b22fada docs: remove citiesFileOverride from config-file.md; fixes #5682 (#5683)
as a result of PR #5301, the citiesFileOverride  variable is not used anymore.
2023-12-13 14:54:51 -05:00
Jason Rasmussen
b34abf25f0 feat(server): server-side events (#5669) 2023-12-13 12:23:51 -05:00
Jason Rasmussen
36196f2a5d fix(immich-admin): in dev mode (#5670) 2023-12-13 11:10:00 -06:00
martin
f13dce7d0d fix: warning when building web (#5680) 2023-12-13 12:02:26 -05:00
Jan
e5e6fcc46d #5519 shared page redirect to host (#5678)
* #5519 shared page redirect to host

* #5519 file formatting
2023-12-13 16:15:39 +00:00
martin
523d01068f fix(web): no icon with firefox (#5679)
* fix: no icon with firefox

* remove FaviconHeader.svelte
2023-12-13 11:04:06 -05:00
Alex
885eba2b7c fix(mobile): simplify state management in backup selection page (#5655)
* fix(mobile): simplify album selection backup state management

* remove search bar'

* log available albums
2023-12-12 21:06:04 -06:00
Alex
f7429c3615 docs: recap 2023 (#5665)
* docs: recap 2023

* fix: format
2023-12-13 03:03:15 +00:00
shenlong
ec0526dbcb chore(mobile): move mocktail to dev dep (#5666)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-12 20:49:14 -06:00
martin
7a9a9473d1 fix: prevent loop on search (#5664) 2023-12-12 16:55:18 -06:00
Jason Rasmussen
a0b2cbe123 feat: update arch diagram (#5662)
* feat: update arch diagram

* chore: alt test
2023-12-12 16:16:39 -06:00
bo0tzz
05550647eb docs: FAQ for thumbnail jobs (#5661) 2023-12-12 22:39:56 +01:00
Alex
c7df800d27 fix(mobile): Fix upload hang on iOS when deleting stale files (#5658)
* fix(mobile): Fix upload hang on iOS when deleting stale files

* Cleaner fix
2023-12-12 11:36:37 -06:00
martin
c602eaea4a feat(web): automatically update user info (#5647)
* use svelte store

* fix: websocket error when not authenticated

* more routes
2023-12-12 10:35:28 -06:00
Jason Rasmussen
cbca69841a refactor(server): immich file responses (#5641)
* refactor(server): immich file response

* chore: open api

* chore: tests

* chore: fix logger import

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-12 08:58:25 -06:00
Alex
af7c4ae090 fix(mobile): better error message (#5653) 2023-12-12 08:58:13 -06:00
martin
fba9e784fb feat: use <a> tag for albums in list view (#5645)
* fix: multiple improvements

* pr feedback

* optimize
2023-12-11 20:35:57 -06:00
shenlong
fb4b4e5895 fix: handle livePhotos using originFileWithSubType (#5602)
* fix: handle livePhotos using originFileWithSubType

* remove livePhoto asset cache

* fetch live photo video name from entity

* fix: video file not detected

* chore: pull main

* fix: set correct header

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-12-11 20:20:36 -06:00
Alex Tran
c8da1c07dc chore: add _redirects to docs for CloudFlare 2023-12-11 16:13:39 -06:00
Alex Tran
ed05785005 chore: remove orphaned package-lock.json 2023-12-11 16:01:33 -06:00
Jason Rasmussen
81603fddc8 chore: fix ssr in dev (#5637) 2023-12-11 14:19:27 -06:00
Jason Rasmussen
ed4358741e feat(web): re-add open graph tags for public share links (#5635)
* feat: re-add open graph tags for public share links

* fix: undefined in html

* chore: tests
2023-12-11 13:37:47 -06:00
renovate[bot]
ac2a36bd53 chore(deps): pin tensorchord/pgvecto-rs docker tag to 0335a1a (#5632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-11 14:00:46 -05:00
Łukasz Wawrzyk
f6ef226b64 chore(mobile): put delete button before metadata editing (#5633) 2023-12-11 12:53:11 -06:00
pjsxw
f798e9beed docs: update backup-and-restore.md (#5616)
Removes `BACKUP_KEEP_NUM` option from docker-compose example for database dumping, since it no longer exists in the linked image. 

The image has sensible defaults for backups to keep (7 daily, 4 weekly, 6 monthly), so I haven't replaced the argument with an alternative.
2023-12-11 10:57:30 -06:00
renovate[bot]
08570875eb chore(deps): update redis:6.2-alpine docker digest to b6124ab (#5599)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-11 10:38:26 -06:00
Alex
64e985d600 fix(mobile): Revert - upload motion and live part of LivePhotos together (#5601) 2023-12-11 10:38:02 -06:00
Sushain Cherivirala
e3e4fb40fd fix(server): don't associate assets with Null Island (#5623)
* Don't associate assets with Null Island

* Fix lint
2023-12-11 09:00:23 -06:00
shenlong
960b68b02f fix(mobile): live / motion photo download (#5607)
* reverts: 5566

* fix: stitch livePhoto only in iOS

* fix: PMProgressHandler only on iOS

* ios: fallback to saving image if livephoto fails

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-10 09:56:39 -06:00
Jason Rasmussen
33529d1d9b refactor(server): auth dto (#5593)
* refactor: AuthUserDto => AuthDto

* refactor: reorganize auth-dto

* refactor: AuthUser() => Auth()
2023-12-09 23:34:12 -05:00
Mert
8057c375ba docs: remove typesense from faq (#5600) 2023-12-10 03:23:40 +00:00
renovate[bot]
d2ad01cd2f chore(deps): update python:3.11-slim-bookworm docker digest to cfd7ed5 (#5572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-09 21:15:15 -06:00
Jason Rasmussen
8e07b35786 docs: add sponsor link (#5597) 2023-12-09 20:59:00 -06:00
Jason Rasmussen
b7b4483a33 fix(server): connection aborted logging (#5595) 2023-12-09 20:46:56 -06:00
shenlong
3a794d7a2b fix: use avatarColor as the text background when no avatar available (#5566)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-09 20:32:39 -06:00
shenlong
68c0112aaa fix(mobile): memory lane not displayed in mobile app (#5587)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-09 20:32:20 -06:00
shenlong
8847ebeef2 fix(mobile): mobile album sort not persisting (#5584)
* chore(deps): use mocktail instead of mockito

* refactor: move stubs to fixtures/

* fix: fetch assetsortmode based on storeindex

* test: validate AlbumSortByOptions provider

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-09 20:31:23 -06:00
Jon Howell
188cdf9367 docs: Add tip showing common error messages (#5592)
I encountered two errors trying to use Ubuntu-distributed docker. These tips make the error messages searchable, with a pointer to the solution (use docker's packages).
2023-12-10 01:05:58 +00:00
Jason Rasmussen
6acd8eb4ba chore(server): faster shutdown (#5577)
* chore(server): faster shutdown

* fix: e2e test entrypoint
2023-12-08 21:58:07 -05:00
Jason Rasmussen
92b4284b5a feat(server): use postgres-adapter for websockets (#5569)
* feat(server): use postgres-adapter for websockets

* refactor: create attachment table via migration
2023-12-08 20:38:25 -05:00
Alex
a426ea8fbc fix(web): remove always on delay badge (#5574) 2023-12-08 18:19:42 -06:00
Alex
f206cb9403 chore(server): simplify search face query and better clustering (#5573)
* chore(server): simplify search face query and better clustering

* update sql

* Use correct syntax for utilizing the index

* Update sql
2023-12-08 16:26:17 -06:00
renovate[bot]
2234394aa6 chore(deps): pin tensorchord/pgvecto-rs docker tag to 0335a1a (#5570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-08 14:38:45 -05:00
renovate[bot]
8a6284569a chore(deps): pin dependencies (#5567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-08 14:24:17 -05:00
renovate[bot]
80591ea609 fix(deps): update server (#5506)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-08 13:57:05 -05:00
Mert
2553c54b26 fix(server): select asset face columns explicitly (#5564)
* select columns explicitly

* updated sql

* formatting
2023-12-08 12:43:35 -06:00
Jason Rasmussen
2f4ee622ab chore(deps): remove unused cron types (#5563) 2023-12-08 12:08:27 -05:00
Jason Rasmussen
02d55644e5 fix(deps): bump sharp (#5509)
* fix(deps): bump sharp

* fixed sharp dependencies

* chore: use date tag

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2023-12-08 11:52:43 -05:00
Jason Rasmussen
1e99ba8167 feat: use pgvecto.rs (#3605) 2023-12-08 11:15:46 -05:00
431 changed files with 7164 additions and 5630 deletions

29
.github/release.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
changelog:
categories:
- title: Breaking Changes 🛠
labels:
- breaking-change
- title: Server
labels:
- 🗄server
- title: Mobile
labels:
- 📱mobile
- title: Web
labels:
- 🖥web
- title: Machine Learning
labels:
- 🧠machine-learning
- title: CLI
labels:
- cli
- title: Documentation
labels:
- documentation
- title: Dependency updates
labels:
- renovate
- title: Other changes
labels:
- "*"

View File

@@ -69,7 +69,7 @@ jobs:
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
- name: Publish Android Artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: release-apk-signed
path: mobile/build/app/outputs/flutter-apk/*.apk

View File

@@ -46,7 +46,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -60,7 +60,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -73,6 +73,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

13
.github/workflows/pr-require-label.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Enforce PR labels
on:
pull_request:
types: [labeled, unlabeled, opened, edited, synchronize]
jobs:
enforce-label:
name: Enforce label
runs-on: ubuntu-latest
steps:
- if: toJson(github.event.pull_request.labels) == '[]'
run: exit 1

View File

@@ -69,7 +69,7 @@ jobs:
token: ${{ secrets.ORG_RELEASE_TOKEN }}
- name: Download APK
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: release-apk-signed

View File

@@ -213,7 +213,7 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: postgres@sha256:6dfee32131933ab4ca25a00360c3f427fdc134de56f9a90c6c9a4956b48aea85
image: tensorchord/pgvecto-rs:pg14-v0.1.11@sha256:0335a1a22f8c5dd1b697f14f079934f5152eaaa216c09b61e293be285491f8ee
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
@@ -261,6 +261,8 @@ jobs:
- name: Run SQL generation
run: npm run sql:generate
env:
DB_URL: postgres://postgres:postgres@localhost:5432/immich
- name: Find file changes
uses: tj-actions/verify-changed-files@v13.1

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ coverage
mobile/gradle.properties
mobile/openapi/pubspec.lock
mobile/*.jks
mobile/libisar.dylib

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -355,12 +355,6 @@ export interface AllJobStatusResponseDto {
* @memberof AllJobStatusResponseDto
*/
'backgroundTask': JobStatusDto;
/**
*
* @type {JobStatusDto}
* @memberof AllJobStatusResponseDto
*/
'clipEncoding': JobStatusDto;
/**
*
* @type {JobStatusDto}
@@ -403,6 +397,12 @@ export interface AllJobStatusResponseDto {
* @memberof AllJobStatusResponseDto
*/
'sidecar': JobStatusDto;
/**
*
* @type {JobStatusDto}
* @memberof AllJobStatusResponseDto
*/
'smartSearch': JobStatusDto;
/**
*
* @type {JobStatusDto}
@@ -2017,7 +2017,7 @@ export const JobName = {
VideoConversion: 'videoConversion',
ObjectTagging: 'objectTagging',
RecognizeFaces: 'recognizeFaces',
ClipEncoding: 'clipEncoding',
SmartSearch: 'smartSearch',
BackgroundTask: 'backgroundTask',
StorageTemplateMigration: 'storageTemplateMigration',
Migration: 'migration',
@@ -2175,6 +2175,24 @@ export const LibraryType = {
export type LibraryType = typeof LibraryType[keyof typeof LibraryType];
/**
*
* @export
* @enum {string}
*/
export const LogLevel = {
Verbose: 'verbose',
Debug: 'debug',
Log: 'log',
Warn: 'warn',
Error: 'error',
Fatal: 'fatal'
} as const;
export type LogLevel = typeof LogLevel[keyof typeof LogLevel];
/**
*
* @export
@@ -3577,6 +3595,12 @@ export interface SystemConfigDto {
* @memberof SystemConfigDto
*/
'library': SystemConfigLibraryDto;
/**
*
* @type {SystemConfigLoggingDto}
* @memberof SystemConfigDto
*/
'logging': SystemConfigLoggingDto;
/**
*
* @type {SystemConfigMachineLearningDto}
@@ -3761,12 +3785,6 @@ export interface SystemConfigJobDto {
* @memberof SystemConfigJobDto
*/
'backgroundTask': JobSettingsDto;
/**
*
* @type {JobSettingsDto}
* @memberof SystemConfigJobDto
*/
'clipEncoding': JobSettingsDto;
/**
*
* @type {JobSettingsDto}
@@ -3809,6 +3827,12 @@ export interface SystemConfigJobDto {
* @memberof SystemConfigJobDto
*/
'sidecar': JobSettingsDto;
/**
*
* @type {JobSettingsDto}
* @memberof SystemConfigJobDto
*/
'smartSearch': JobSettingsDto;
/**
*
* @type {JobSettingsDto}
@@ -3860,6 +3884,27 @@ export interface SystemConfigLibraryScanDto {
*/
'enabled': boolean;
}
/**
*
* @export
* @interface SystemConfigLoggingDto
*/
export interface SystemConfigLoggingDto {
/**
*
* @type {boolean}
* @memberof SystemConfigLoggingDto
*/
'enabled': boolean;
/**
*
* @type {LogLevel}
* @memberof SystemConfigLoggingDto
*/
'level': LogLevel;
}
/**
*
* @export
@@ -14565,22 +14610,12 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio
* @param {string} [query]
* @param {boolean} [clip]
* @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type]
* @param {boolean} [isFavorite]
* @param {boolean} [isArchived]
* @param {string} [exifInfoCity]
* @param {string} [exifInfoState]
* @param {string} [exifInfoCountry]
* @param {string} [exifInfoMake]
* @param {string} [exifInfoModel]
* @param {string} [exifInfoProjectionType]
* @param {Array<string>} [smartInfoObjects]
* @param {Array<string>} [smartInfoTags]
* @param {boolean} [recent]
* @param {boolean} [motion]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array<string>, smartInfoTags?: Array<string>, recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/search`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@@ -14618,46 +14653,6 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio
localVarQueryParameter['type'] = type;
}
if (isFavorite !== undefined) {
localVarQueryParameter['isFavorite'] = isFavorite;
}
if (isArchived !== undefined) {
localVarQueryParameter['isArchived'] = isArchived;
}
if (exifInfoCity !== undefined) {
localVarQueryParameter['exifInfo.city'] = exifInfoCity;
}
if (exifInfoState !== undefined) {
localVarQueryParameter['exifInfo.state'] = exifInfoState;
}
if (exifInfoCountry !== undefined) {
localVarQueryParameter['exifInfo.country'] = exifInfoCountry;
}
if (exifInfoMake !== undefined) {
localVarQueryParameter['exifInfo.make'] = exifInfoMake;
}
if (exifInfoModel !== undefined) {
localVarQueryParameter['exifInfo.model'] = exifInfoModel;
}
if (exifInfoProjectionType !== undefined) {
localVarQueryParameter['exifInfo.projectionType'] = exifInfoProjectionType;
}
if (smartInfoObjects) {
localVarQueryParameter['smartInfo.objects'] = smartInfoObjects;
}
if (smartInfoTags) {
localVarQueryParameter['smartInfo.tags'] = smartInfoTags;
}
if (recent !== undefined) {
localVarQueryParameter['recent'] = recent;
}
@@ -14752,23 +14747,13 @@ export const SearchApiFp = function(configuration?: Configuration) {
* @param {string} [query]
* @param {boolean} [clip]
* @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type]
* @param {boolean} [isFavorite]
* @param {boolean} [isArchived]
* @param {string} [exifInfoCity]
* @param {string} [exifInfoState]
* @param {string} [exifInfoCountry]
* @param {string} [exifInfoMake]
* @param {string} [exifInfoModel]
* @param {string} [exifInfoProjectionType]
* @param {Array<string>} [smartInfoObjects]
* @param {Array<string>} [smartInfoTags]
* @param {boolean} [recent]
* @param {boolean} [motion]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array<string>, smartInfoTags?: Array<string>, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SearchResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, exifInfoProjectionType, smartInfoObjects, smartInfoTags, recent, motion, options);
async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SearchResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, recent, motion, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
@@ -14807,7 +14792,7 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat
* @throws {RequiredError}
*/
search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig): AxiosPromise<SearchResponseDto> {
return localVarFp.search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(axios, basePath));
return localVarFp.search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.recent, requestParameters.motion, options).then((request) => request(axios, basePath));
},
/**
*
@@ -14855,76 +14840,6 @@ export interface SearchApiSearchRequest {
*/
readonly type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'
/**
*
* @type {boolean}
* @memberof SearchApiSearch
*/
readonly isFavorite?: boolean
/**
*
* @type {boolean}
* @memberof SearchApiSearch
*/
readonly isArchived?: boolean
/**
*
* @type {string}
* @memberof SearchApiSearch
*/
readonly exifInfoCity?: string
/**
*
* @type {string}
* @memberof SearchApiSearch
*/
readonly exifInfoState?: string
/**
*
* @type {string}
* @memberof SearchApiSearch
*/
readonly exifInfoCountry?: string
/**
*
* @type {string}
* @memberof SearchApiSearch
*/
readonly exifInfoMake?: string
/**
*
* @type {string}
* @memberof SearchApiSearch
*/
readonly exifInfoModel?: string
/**
*
* @type {string}
* @memberof SearchApiSearch
*/
readonly exifInfoProjectionType?: string
/**
*
* @type {Array<string>}
* @memberof SearchApiSearch
*/
readonly smartInfoObjects?: Array<string>
/**
*
* @type {Array<string>}
* @memberof SearchApiSearch
*/
readonly smartInfoTags?: Array<string>
/**
*
* @type {boolean}
@@ -14986,7 +14901,7 @@ export class SearchApi extends BaseAPI {
* @memberof SearchApi
*/
public search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig) {
return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath));
return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath));
}
/**
@@ -17973,7 +17888,7 @@ export const UserApiFp = function(configuration?: Configuration) {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getProfileImage(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
async getProfileImage(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<File>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getProfileImage(id, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
@@ -18075,7 +17990,7 @@ export const UserApiFactory = function (configuration?: Configuration, basePath?
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getProfileImage(requestParameters: UserApiGetProfileImageRequest, options?: AxiosRequestConfig): AxiosPromise<object> {
getProfileImage(requestParameters: UserApiGetProfileImageRequest, options?: AxiosRequestConfig): AxiosPromise<File> {
return localVarFp.getProfileImage(requestParameters.id, options).then((request) => request(axios, basePath));
},
/**

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.90.2
* The version of the OpenAPI document: 1.91.1
*
*
* 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.2
* The version of the OpenAPI document: 1.91.1
*
*
* 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.2
* The version of the OpenAPI document: 1.91.1
*
*
* 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.2
* The version of the OpenAPI document: 1.91.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -29,7 +29,7 @@ x-server-build: &server-common
services:
immich-server:
container_name: immich_server
command: npm run start:debug immich
command: [ "/usr/src/app/bin/immich-dev", "immich" ]
<<: *server-common
ports:
- 3001:3001
@@ -37,11 +37,10 @@ services:
depends_on:
- redis
- database
- typesense
immich-microservices:
container_name: immich_microservices
command: npm run start:debug microservices
command: [ "/usr/src/app/bin/immich-dev", "microservices" ]
<<: *server-common
# extends:
# file: hwaccel.yml
@@ -51,7 +50,6 @@ services:
depends_on:
- database
- immich-server
- typesense
immich-web:
container_name: immich_web
@@ -95,24 +93,13 @@ services:
- database
restart: unless-stopped
typesense:
container_name: immich_typesense
image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- ${UPLOAD_LOCATION}/typesense:/data
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
image: redis:6.2-alpine@sha256:b6124ab2e45cc332e16398022a411d7e37181f21ff7874835e0180f56a09e82a
database:
container_name: immich_postgres
image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
image: tensorchord/pgvecto-rs:pg14-v0.1.11@sha256:0335a1a22f8c5dd1b697f14f079934f5152eaaa216c09b61e293be285491f8ee
env_file:
- .env
environment:

View File

@@ -24,7 +24,6 @@ services:
depends_on:
- redis
- database
- typesense
immich-microservices:
container_name: immich_microservices
@@ -36,7 +35,6 @@ services:
depends_on:
- redis
- database
- typesense
- immich-server
immich-machine-learning:
@@ -51,26 +49,14 @@ services:
- .env
restart: always
typesense:
container_name: immich_typesense
image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- ${UPLOAD_LOCATION}/typesense:/data
restart: always
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
image: redis:6.2-alpine@sha256:b6124ab2e45cc332e16398022a411d7e37181f21ff7874835e0180f56a09e82a
restart: always
database:
container_name: immich_postgres
image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
image: tensorchord/pgvecto-rs:pg14-v0.1.11@sha256:0335a1a22f8c5dd1b697f14f079934f5152eaaa216c09b61e293be285491f8ee
env_file:
- .env
environment:

View File

@@ -9,7 +9,8 @@ services:
context: ../
dockerfile: server/Dockerfile
target: dev
command: npm run test:e2e
entrypoint: [ "/usr/local/bin/npm", "run" ]
command: test:e2e
volumes:
- ../server:/usr/src/app
- /usr/src/app/node_modules
@@ -23,8 +24,7 @@ services:
- database
database:
image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
command: -c fsync=off
image: tensorchord/pgvecto-rs:pg14-v0.1.11@sha256:0335a1a22f8c5dd1b697f14f079934f5152eaaa216c09b61e293be285491f8ee
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres

View File

@@ -25,7 +25,6 @@ services:
depends_on:
- redis
- database
- typesense
restart: always
immich-microservices:
@@ -43,7 +42,6 @@ services:
depends_on:
- redis
- database
- typesense
restart: always
immich-machine-learning:
@@ -55,26 +53,14 @@ services:
- .env
restart: always
typesense:
container_name: immich_typesense
image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- tsdata:/data
restart: always
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
image: redis:6.2-alpine@sha256:b6124ab2e45cc332e16398022a411d7e37181f21ff7874835e0180f56a09e82a
restart: always
database:
container_name: immich_postgres
image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
image: tensorchord/pgvecto-rs:pg14-v0.1.11@sha256:0335a1a22f8c5dd1b697f14f079934f5152eaaa216c09b61e293be285491f8ee
env_file:
- .env
environment:
@@ -88,4 +74,3 @@ services:
volumes:
pgdata:
model-cache:
tsdata:

View File

@@ -6,8 +6,7 @@ UPLOAD_LOCATION=./library
# The Immich version to use. You can pin this to a specific version like "v1.71.0"
IMMICH_VERSION=release
# Connection secrets for postgres and typesense. You should change these to random passwords
TYPESENSE_API_KEY=some-random-text
# Connection secret for postgres. You should change it to a random password
DB_PASSWORD=postgres
# The values below this line do not need to be changed

View File

@@ -0,0 +1,70 @@
---
title: Immich Recap 2023
authors: [alextran]
tags: [update, recap-2023]
---
Hi everyone,
Alex from Immich here.
We are entering the last few weeks of 2023, and it has been quite a year for Immich. The project has grown so much in terms of users, developers, features, maturity, and the community around it. When I started working on Immich, it was simply a challenge for myself and an opportunity to learn new technologies, crafting something fun and useful for my wife during my free time to satisfy my urge to build and create things. I never thought it would become so popular and help so many people. At the end of the day, all we have is memory. I am proud that the team and I have created something to make storing and viewing those precious memories easier without restrictions and without sacrificing our privacy. As the year closes, heres a recap of everything the project accomplished in 2023.
# Milestones
- Public shared links
- Favorites page
- Immich turned 1
- Material Design 3 on the mobile app
- Auto-link LivePhotos server-side
- iOS background backup
- Explore page
- CLIP search
- Search by metadata
- Responsive web app
- Archive page
- Asset descriptions
- 10,000 stars on GitHub
- Manage auth devices
- Map view
- Facial recognition, clustering, searching, renaming, and person management
- Partner sharing and unifying timeline between partners' users
- Custom storage label
- XMP sidecar reading
- RAW file formats
- Justified layout on the web
- Memories
- Multi-select via SHIFT
- Android Motion Photos
- 360° Photos
- Album description
- Album performance improvements (time buckets)
- Video hardware transcoding
- Slideshow mode on the web
- Configuration file
- External libraries
- Trash page
- Custom theme
- Asset Stacking
- 20,000 stars on GitHub
- Shared album activity and comments
- CLI v2
- Down to 5 containers (from 8)
# Fun Statistics
- We have gone from the release version `1.41.0` to `1.90.0` at the time of writing. On average, we see a release every 7 days.
- According to GitHub's metrics, the `immich-server` container image has been pulled almost _4 million_ times.
- According to mobile app store metrics, we have 22,000 installations on Android and 6700 installation units on iOS (opt-in only).
- Immich is making around $1200/month on average from donations. (Thank you all so much!)
- We were guests on two podcasts:
- [Self-hosted](https://selfhosted.show/110)
- [The Vergecast](https://www.theverge.com/23938533/self-hosting-local-first-software-vergecast)
- There are over 4,500 members on the Discord server.
- We have over 22,000 stars on the main GitHub repository, gaining 15,000 stars since January 2023.
Diving into the next year, the team will continue to build on the foundation we have laid out over the past year, implementing more advanced features for searching, organizing, and sharing between users. Bugs will continue to be squashed and conquered. “Shit Alex wrote'' code will continue to be replaced by beautiful, clean code from Jason, Zack, Boet, Daniel, Osorin, Mert, Fynn, Marty, Martin, and Jonathan. The team has my eternal gratitude for creating a welcoming environment for new contributors, helping, teaching, and learning from each other. Ive realized that hardly a day has gone by where the team hasnt been in communication about Immich related topics over the past year.
My long-term goal is to help hone Immich into a diamond in the FOSS space, where the UI, UX, development experiences, documentation, and quality are at a high standard while remaining free for everybody to use.
I hope you enjoy Immich and have a happy and peaceful holiday.

View File

@@ -26,11 +26,10 @@ Immich optionally uses machine learning for several features. However, it can be
### How can I lower Immich's CPU usage?
The initial backup is the most intensive due to the number of jobs running. The most CPU-intensive ones are transcoding and machine learning jobs (Tag Images, Encode CLIP, Recognize Faces), and to a lesser extent thumbnail generation. Here are some ways to lower their CPU usage:
The initial backup is the most intensive due to the number of jobs running. The most CPU-intensive ones are transcoding and machine learning jobs (Tag Images, Smart Search, Recognize Faces), and to a lesser extent thumbnail generation. Here are some ways to lower their CPU usage:
- Lower the job concurrency for these jobs to 1.
- Under Settings > Transcoding Settings > Threads, set the number of threads to a low number like 1 or 2.
- Set the `TYPESENSE_THREAD_POOL_SIZE` environmental variable and restart the Typesense container. For instance, `TYPESENSE_THREAD_POOL_SIZE=8` will limit it to 8 threads.
- Under Settings > Machine Learning Settings > Facial Recognition > Model Name, you can change the facial recognition model to `buffalo_s` instead of `buffalo_l`. The former is a smaller and faster model, albeit not as good.
- You _must_ re-run the Recognize Faces job for all images after this for facial recognition on new images to work properly.
- If these changes are not enough, see [below](/docs/FAQ.md#how-can-i-disable-machine-learning) for how you can disable machine learning.
@@ -45,14 +44,6 @@ Machine learning can be disabled under Settings > Machine Learning Settings, eit
However, disabling all jobs will not disable the machine learning service itself. To prevent it from starting up at all in this case, you can comment out the `immich-machine-learning` section of the docker-compose.yml.
### How can I disable TypeSense?
:::info
Disabling Typesense will result in a poor search experience since searching is reliant on it.
:::
You can disable Typesense by commenting out the `immich-typesense` section of the docker-compose.yml and setting `TYPESENSE_ENABLED=false` in your .env file.
### I'm getting errors about models being corrupt or failing to download. What do I do?
You can delete the model cache volume, which is where models are downloaded. This will give the service a clean environment to download the model again.
@@ -69,6 +60,10 @@ This is fixed by running the storage migration job.
The default image tagging model is relatively small. You can change this for a larger model like `google/vit-base-patch16-224` by setting the model name under Settings > Machine Learning Settings > Image Tagging. You can then re-run the Image Tagging job to get improved tags.
### Why are there so many thumbnail generation jobs?
Immich generates three thumbnails for each asset (blurred, small, and large), as well as a thumbnail for each recognized face.
### How can I see Immich logs?
Most Immich components are typically deployed using docker. To see logs for deployed docker containers, you can use the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/), specifically the `docker logs` command. For examples, see [Docker Help](/docs/guides/docker-help.md)

View File

@@ -44,7 +44,6 @@ services:
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
SCHEDULE: "@daily"
BACKUP_NUM_KEEP: 7
BACKUP_DIR: /db_dumps
volumes:
- ./db_dumps:/db_dumps

View File

@@ -2,15 +2,17 @@
sidebar_position: 1
---
import AppArchitecture from './img/app-architecture.png';
# Architecture
Immich uses a traditional client-server design, with a dedicated database for data persistence. The frontend clients communicate with backend services over HTTP using REST APIs.
Immich uses a traditional client-server design, with a dedicated database for data persistence. The frontend clients communicate with backend services over HTTP using REST APIs. Below is a high level diagram of the architecture.
## High Level Diagram
![Immich Architecture](./img/app-architecture.png)
<img alt="Immich Architecture" src={AppArchitecture} className="p-4 dark:bg-immich-dark-primary my-4" />
The diagram shows clients communicating with the server via REST, as well as the flow of database between backend services.
The diagram shows clients communicating with the server's API via REST. The server communicates with downstream systems (i.e. Redis, Postgres, Machine Learning, file system) through repository interfaces. Not shown in the diagram, is that the server is split into two separate containers `immich-server` and `immich-microservices`. The microservices container does not handle API requests or schedule cron jobs, but primarily handles incoming job requests from Redis.
## Clients
@@ -45,7 +47,6 @@ The Immich backend is divided into several services, which are run as individual
1. `immich-machine-learning` - Execute machine learning models
1. `postgres` - Persistent data storage
1. `redis`- Queue management for `immich-microservices`
1. `typesense`- Specialized database for search, specifically with vector comparison features
### Immich Server
@@ -75,7 +76,6 @@ The Immich Microservices image uses the same `Dockerfile` as the Immich Server,
- Object Tagging
- Facial Recognition
- Storage Template Migration
- Search (Typesense synchronization)
- Sidecar (see [XMP Sidecars](/docs/features/xmp-sidecars.md))
- Background jobs (file deletion, user deletion)
@@ -108,9 +108,3 @@ See [Database Migrations](./database-migrations.md) for more information about h
### Redis
Immich uses [Redis](https://redis.com/) via [BullMQ](https://docs.bullmq.io/) to manage job queues. Some jobs trigger subsequent jobs. For example, object detection relies on thumbnail generation and automatically run after one is generated.
### Typesense
Immich synchronizes some of the Postgres data into Typesense, so it can execute vector related queries in order to implement certain features including, facial recognition and CLIP search.
<!-- - [NGINX](https://www.nginx.com/) for internal communication between containers and load balancing when scaling. -->

View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2023-12-12T20:57:14.605Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" etag="df4JlXsRZhazQ-YEVHpD" version="22.1.7" type="google">
<diagram name="Page-1" id="B4lqvvtjK-gtNbJBlj3M">
<mxGraphModel dx="1434" dy="798" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="skFinV3TG75uy4JOHTMt-8" value="" style="verticalLabelPosition=bottom;verticalAlign=top;html=1;shape=mxgraph.basic.polygon;polyCoords=[[0.25,0],[0.75,0],[1,0.25],[1,0.75],[0.75,1],[0.25,1],[0,0.75],[0,0.25]];polyline=0;fillColor=#d0cee2;strokeColor=#56517e;strokeWidth=16;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="187" y="28" width="480" height="480" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-57" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.167;entryDx=0;entryDy=0;strokeWidth=3;endArrow=block;endFill=1;entryPerimeter=0;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-11" target="skFinV3TG75uy4JOHTMt-30">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-11" value="Web App&lt;br&gt;(SvelteKit)" style="whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="24" y="88" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-58" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;endArrow=block;endFill=1;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-12" target="skFinV3TG75uy4JOHTMt-30">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-12" value="Mobile App&lt;br&gt;(Flutter)" style="whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="24" y="230" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-59" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.013;entryY=0.863;entryDx=0;entryDy=0;strokeWidth=3;endArrow=block;endFill=1;entryPerimeter=0;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-13" target="skFinV3TG75uy4JOHTMt-30">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-13" value="Immich CLI&lt;br&gt;(TypeScript)" style="whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="24" y="368" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-23" value="" style="verticalLabelPosition=bottom;verticalAlign=top;html=1;shape=mxgraph.basic.polygon;polyCoords=[[0.25,0],[0.75,0],[1,0.25],[1,0.75],[0.75,1],[0.25,1],[0,0.75],[0,0.25]];polyline=0;fillColor=#b1ddf0;strokeColor=#10739e;strokeWidth=2;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="335" y="183" width="170" height="170" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-28" value="&lt;h1&gt;Server&lt;/h1&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="377" y="28" width="100" height="70" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-116" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=3;endArrow=block;endFill=1;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-30" target="skFinV3TG75uy4JOHTMt-23">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="325" y="268" />
<mxPoint x="325" y="268" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-30" value="API &lt;br&gt;Controllers" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b0e3e6;strokeColor=#0e8088;arcSize=0;strokeWidth=1;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;" vertex="1" parent="1">
<mxGeometry x="215" y="170" width="95" height="200" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-43" value="&lt;h2&gt;&lt;span style=&quot;background-color: initial; font-size: 12px; font-weight: normal;&quot;&gt;Repositories&lt;/span&gt;&lt;/h2&gt;&lt;b&gt;&quot;Infra&quot;&lt;br&gt;&lt;/b&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b0e3e6;strokeColor=#0e8088;arcSize=0;strokeWidth=1;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;" vertex="1" parent="1">
<mxGeometry x="543" y="169" width="100" height="198" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-45" value="Cron" style="rhombus;whiteSpace=wrap;html=1;fillColor=#b0e3e6;strokeColor=#0e8088;strokeWidth=1;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="270" y="388" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-46" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.625;exitY=0.15;exitDx=0;exitDy=0;strokeWidth=3;endArrow=block;endFill=1;entryX=0.106;entryY=0.847;entryDx=0;entryDy=0;entryPerimeter=0;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;exitPerimeter=0;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-45" target="skFinV3TG75uy4JOHTMt-23">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-52" value="Postgres" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#dae8fc;strokeColor=#6c8ebf;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="745" y="270" width="60" height="80" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-128" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0;exitDx=0;exitDy=52.5;exitPerimeter=0;strokeColor=#000000;strokeWidth=3;endArrow=block;endFill=1;fillColor=#fad9d5;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-60" target="skFinV3TG75uy4JOHTMt-131">
<mxGeometry relative="1" as="geometry">
<mxPoint x="620" y="350" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-60" value="Redis" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#f8cecc;strokeColor=#b85450;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="745" y="380" width="60" height="80" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-67" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;edgeStyle=orthogonalEdgeStyle;strokeWidth=1;dashed=1;endArrow=none;endFill=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fillColor=#ffff88;strokeColor=#000000;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-74" target="skFinV3TG75uy4JOHTMt-84">
<mxGeometry relative="1" as="geometry">
<mxPoint x="166" y="18.99000000000001" as="sourcePoint" />
<mxPoint x="165" y="508" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-69" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=block;endFill=1;startArrow=none;startFill=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-43" target="skFinV3TG75uy4JOHTMt-52">
<mxGeometry relative="1" as="geometry">
<mxPoint x="645" y="168" as="sourcePoint" />
<mxPoint x="755" y="168" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-74" value="&lt;i style=&quot;font-size: 10px;&quot;&gt;HTTP (OpenAPI)&lt;/i&gt;" style="whiteSpace=wrap;html=1;fillColor=#eeeeee;strokeColor=#36393d;fontSize=10;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="110" y="48" width="105" height="20" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-78" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=block;startFill=1;endArrow=none;endFill=0;strokeWidth=3;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-43" target="skFinV3TG75uy4JOHTMt-23">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-84" value="&lt;i style=&quot;font-size: 10px;&quot;&gt;HTTP (OpenAPI)&lt;/i&gt;" style="whiteSpace=wrap;html=1;fillColor=#eeeeee;strokeColor=#36393d;fontSize=10;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="110" y="468" width="105" height="20" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-85" value="&lt;h2&gt;Core&lt;br&gt;Services&lt;/h2&gt;&lt;div&gt;&lt;b&gt;&quot;Domain&quot;&lt;/b&gt;&lt;/div&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="370" y="208" width="100" height="100" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-87" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=3;endArrow=block;endFill=1;startArrow=none;startFill=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;exitX=1;exitY=0.5;exitDx=0;exitDy=0;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-43" target="skFinV3TG75uy4JOHTMt-94">
<mxGeometry relative="1" as="geometry">
<mxPoint x="635" y="268" as="sourcePoint" />
<mxPoint x="735" y="398" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-94" value="Machine Learning&lt;br&gt;(Python)" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="705" y="48" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-97" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=3;endArrow=block;endFill=1;startArrow=none;startFill=0;entryX=0;entryY=1;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-43" target="skFinV3TG75uy4JOHTMt-101">
<mxGeometry relative="1" as="geometry">
<mxPoint x="655" y="318" as="sourcePoint" />
<mxPoint x="785" y="298" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-101" value="File&lt;br&gt;System" style="rhombus;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="735" y="163" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-117" value="&lt;i style=&quot;font-size: 10px;&quot;&gt;HTTP&lt;/i&gt;" style="whiteSpace=wrap;html=1;fillColor=#eeeeee;strokeColor=#36393d;fontSize=10;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="682" y="163" width="48" height="20" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-129" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;endArrow=block;endFill=1;strokeWidth=3;strokeColor=#000000;fillColor=#fad9d5;entryX=0.888;entryY=0.865;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-131" target="skFinV3TG75uy4JOHTMt-23">
<mxGeometry relative="1" as="geometry">
<mxPoint x="565" y="278" as="sourcePoint" />
<mxPoint x="479" y="340" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-131" value="Jobs" style="whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#b0e3e6;strokeColor=#0e8088;rounded=0;" vertex="1" parent="1">
<mxGeometry x="563" y="340" width="60" height="20" as="geometry" />
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-134" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=3;entryX=0;entryY=0;entryDx=0;entryDy=27.5;entryPerimeter=0;endArrow=block;endFill=1;startArrow=none;startFill=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;fillColor=#fad9d5;strokeColor=#000000;" edge="1" parent="1" source="skFinV3TG75uy4JOHTMt-43" target="skFinV3TG75uy4JOHTMt-60">
<mxGeometry relative="1" as="geometry">
<mxPoint x="634" y="318" as="sourcePoint" />
<mxPoint x="755" y="289" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="skFinV3TG75uy4JOHTMt-140" value="&lt;i style=&quot;font-size: 10px;&quot;&gt;Queue&lt;/i&gt;" style="whiteSpace=wrap;html=1;fillColor=#eeeeee;strokeColor=#36393d;fontSize=10;fontFamily=Overpass;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DOverpass;rounded=0;" vertex="1" parent="1">
<mxGeometry x="679" y="394" width="48" height="20" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 570 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -42,6 +42,7 @@ Usage: immich [options] [command]
Immich command line interface
Options:
-V, --version output the version number
-h, --help display help for command
Commands:

View File

@@ -1,18 +1,10 @@
# Search
Immich uses Typesense as the primary search database to enable high performance search mechanism.
Immich uses Postgres as its search database for both metadata and smart search.
Typesense is a powerful search engine that can be integrated with popular natural language processing (NLP) models like CLIP and SBERT to provide highly accurate and relevant search results. Here are some benefits of using Typesense integrated search for CLIP and SBERT:
Smart search is powered by the [pgvecto.rs](https://github.com/tensorchord/pgvecto.rs) extension, utilizing machine learning models like CLIP to provide relevant search results. This allows for freeform searches without requiring specific keywords in the image or video metadata.
Improved Search Accuracy: Typesense uses a combination of indexing, querying, and ranking algorithms to quickly and accurately retrieve relevant search results. When integrated with CLIP and SBERT, Typesense can leverage the semantic understanding and deep learning capabilities of these models to further improve the accuracy of search results.
Faster Search Response Times: Typesense is optimized for lightning-fast search response times, making it ideal for applications that require near-instantaneous search results. By integrating with CLIP and SBERT, Typesense can reduce the time required to process complex search queries, making it even faster and more efficient.
Enhanced Semantic Search Capabilities: CLIP and SBERT are powerful NLP models that can extract the semantic meaning from text, enabling more nuanced search queries. By integrating with Typesense, these models can help to improve the accuracy of semantic search, enabling users to find the most relevant results based on the true meaning of their query.
Greater Search Flexibility: Typesense provides flexible search capabilities, including fuzzy search, partial search, enabling users to find the information they need quickly and easily. When integrated with CLIP and SBERT, Typesense can offer even greater flexibility, allowing users to refine their search queries using natural language and providing more accurate and relevant results.
(Generated by Chat-GPT4)
Metadata search (prefixed with `m:`) can search specifically by text without the use of a model.
Some search examples:
<img src={require('./img/search-ex-2.webp').default} title='Search Example 1' />

View File

@@ -0,0 +1,103 @@
# External Library
This guide walks you through adding an [External Library](../features/libraries#external-libraries).
This guide assumes you are running Immich in Docker and that the files you wish to access are stored
in a directory on the same machine.
# Mount the directory into the containers.
Edit `docker-compose.yml` to add two new mount points under `volumes:`
```
immich-server:
volumes:
- ${EXTERNAL_PATH}:/usr/src/app/external
```
Be sure to add exactly the same line to both `immich-server:` and `immich-microservices:`.
[Question for the devs: Is editing docker-compose.yml really the desirable way to solve this problem?
I assumed user changes were supposed to be kept to .env?]
Edit `.env` to define `EXTERNAL_PATH`, substituting in the correct path for your computer:
```
EXTERNAL_PATH=<your-path-here>
```
On my computer, for example, I use this path:
```
EXTERNAL_PATH=/home/tenino/photos
```
Restart Immich.
```
docker compose down
docker compose up -d
```
# Set the External Path
In the Immich web UI:
- click the **Administration** link in the upper right corner.
<img src={require('./img/administration-link.png').default} width="50%" title="Administration link" />
- Select the **Users** tab
<img src={require('./img/users-tab.png').default} width="50%" title="Users tab" />
- Select the **pencil** next to your user ID
<img src={require('./img/pencil.png').default} width="50%" title="Pencil" />
- Fill in the **External Path** field with `/usr/src/app/external`
<img src={require('./img/external-path.png').default} width="50%" title="External Path field" />
Notice this matches the path _inside the container_ where we mounted your photos.
The purpose of the external path field is for administrators who have multiple users
on their Immich instance. It lets you prevent other authorized users from
navigating to your external library.
# Import the library
In the Immich web UI:
- Click your user avatar in the upper-right corner (circle with your initials)
<img src={require('./img/user-avatar.png').default} width="50%" title="User avatar" />
- Click **Account Settings**
<img src={require('./img/account-settings.png').default} width="50%" title="Account Settings button" />
- Click to expand **Libraries**
<img src={require('./img/libraries-dropdown.png').default} width="50%" title="Libraries dropdown" />
- Click the **Create External Library** button
<img src={require('./img/create-external-library-button.png').default} width="50%" title="Create External Library button" />
- Click the three-dots menu and select **Edit Import Paths**
<img src={require('./img/edit-import-paths.png').default} width="50%" title="Edit Import Paths menu option" />
- Click \*_Add path_
<img src={require('./img/add-path-button.png').default} width="50%" title="Add Path button" />
- Enter **.** as the path and click Add
<img src={require('./img/add-path-field.png').default} width="50%" title="Add Path field" />
- Save the new path
<img src={require('./img/path-save.png').default} width="50%" title="Path Save button" />
- Click the three-dots menu and select **Scan New Library Files** [I'm not sure whether this is necessary]
<img src={require('./img/scan-new-library-files.png').default} width="50%" title="Scan New Library Files menu option" />
# Confirm stuff is happening
- Click **Administration**
<img src={require('./img/administration-link.png').default} width="50%" title="Administration link" />
- Select the **Jobs** tab
<img src={require('./img/jobs-tab.png').default} width="50%" title="Jobs tab" />
- You should see non-zero Active jobs for
Library, Generate Thumbnails, and Extract Metadata.
<img src={require('./img/job-status.png').default} width="50%" title="Job Status display" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -91,8 +91,7 @@ The default configuration looks like this:
"tileUrl": "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
},
"reverseGeocoding": {
"enabled": true,
"citiesFileOverride": "cities500"
"enabled": true
},
"oauth": {
"enabled": false,

View File

@@ -2,6 +2,9 @@
sidebar_position: 30
---
import CodeBlock from '@theme/CodeBlock';
import ExampleEnv from '!!raw-loader!../../../docker/example.env';
# Docker Compose [Recommended]
Docker Compose is the recommended method to run Immich in production. Below are the steps to deploy Immich with Docker Compose.
@@ -40,104 +43,15 @@ Optionally, you can use the [`hwaccel.yml`][hw-file] file to enable hardware acc
### Step 2 - Populate the .env file with custom values
<details>
<summary>Example <code>.env</code> content</summary>
```bash
###################################################################################
# Database
###################################################################################
DB_HOSTNAME=immich_postgres
DB_USERNAME=postgres
DB_PASSWORD=postgres
DB_DATABASE_NAME=immich
# Optional Database settings:
# DB_PORT=5432
###################################################################################
# Redis
###################################################################################
REDIS_HOSTNAME=immich_redis
# Optional Redis settings:
# Note: these parameters are not automatically passed to the Redis Container
# to do so, please edit the docker-compose.yml file as well. Redis is not configured
# via environment variables, only redis.conf or the command line
# REDIS_PORT=6379
# REDIS_DBINDEX=0
# REDIS_PASSWORD=
# REDIS_SOCKET=
###################################################################################
# Upload File Location
#
# This is the location where uploaded files are stored.
###################################################################################
UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_backup
###################################################################################
# Log message level - [simple|verbose]
###################################################################################
LOG_LEVEL=simple
###################################################################################
# Typesense
###################################################################################
# TYPESENSE_ENABLED=false
TYPESENSE_API_KEY=some-random-text
# TYPESENSE_HOST: typesense
# TYPESENSE_PORT: 8108
# TYPESENSE_PROTOCOL: http
###################################################################################
# Reverse Geocoding
#
# Reverse geocoding is done locally which has a small impact on memory usage
# This memory usage can be altered by changing the REVERSE_GEOCODING_PRECISION variable
# This ranges from 0-3 with 3 being the most precise
# 3 - Cities > 500 population: ~200MB RAM
# 2 - Cities > 1000 population: ~150MB RAM
# 1 - Cities > 5000 population: ~80MB RAM
# 0 - Cities > 15000 population: ~40MB RAM
####################################################################################
# DISABLE_REVERSE_GEOCODING=false
# REVERSE_GEOCODING_PRECISION=3
####################################################################################
# WEB - Optional
#
# Custom message on the login page, should be written in HTML form.
# For example:
# PUBLIC_LOGIN_PAGE_MESSAGE="This is a demo instance of Immich.<br><br>Email: <i>demo@demo.de</i><br>Password: <i>demo</i>"
####################################################################################
PUBLIC_LOGIN_PAGE_MESSAGE="My Family Photos and Videos Backup Server"
###################################################################################
# Immich Version - Optional
#
# This allows all immich docker images to be pinned to a specific version. By default,
# the version is "release" but could be a specific version, like "v1.59.0".
###################################################################################
#IMMICH_VERSION=
```
<summary>
Example <code>.env</code> content
</summary>
<CodeBlock language="bash">{ExampleEnv}</CodeBlock>
</details>
- Populate custom database information if necessary.
- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets.
- Consider changing `DB_PASSWORD` to something randomly generated
- Consider changing `TYPESENSE_API_KEY` to something randomly generated
### Step 3 - Start the containers
@@ -147,6 +61,19 @@ From the directory you created in Step 1, (which should now contain your customi
docker compose up -d
```
:::tip
If you get an error `unknown shorthand flag: 'd' in -d`, you are probably running the wrong Docker version. (This happens, for example, with the docker.io package in Ubuntu 22.04.3 LTS.) You can correct the problem by `apt remove`ing Ubuntu's docker.io package and installing docker and docker-compose via [Docker's official repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository).
Note that the correct command really is `docker compose`, not `docker-compose`. If you try the latter on vanilla Ubuntu 22.04 it will fail in a different way:
```
The Compose file './docker-compose.yml' is invalid because:
'name' does not match any of the regexes: '^x-'
```
See the previous paragraph about installing from the official docker repository.
:::
:::tip
For more information on how to use the application, please refer to the [Post Installation](/docs/install/post-install.mdx) guide.
:::

View File

@@ -17,10 +17,10 @@ If this should not work, try running `docker compose up -d --force-recreate`.
## Docker Compose
| Variable | Description | Default | Services |
| :---------------- | :-------------------- | :-------: | :------------------------------------------------------------- |
| `IMMICH_VERSION` | Image tags | `release` | server, microservices, machine learning, web, proxy, typesense |
| `UPLOAD_LOCATION` | Host Path for uploads | | server, microservices |
| Variable | Description | Default | Services |
| :---------------- | :-------------------- | :-------: | :-------------------------------------------------- |
| `IMMICH_VERSION` | Image tags | `release` | server, microservices, machine learning, web, proxy |
| `UPLOAD_LOCATION` | Host Path for uploads | | server, microservices |
:::tip
@@ -30,14 +30,15 @@ These environment variables are used by the `docker-compose.yml` file and do **N
## General
| Variable | Description | Default | Services |
| :-------------------------- | :------------------------------------------- | :----------: | :------------------------------------------- |
| `TZ` | Timezone | | microservices |
| `NODE_ENV` | Environment (production, development) | `production` | server, microservices, machine learning, web |
| `LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices |
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload` | server, microservices |
| `PUBLIC_LOGIN_PAGE_MESSAGE` | Public Login Page Message | | web |
| `IMMICH_CONFIG_FILE` | Path to config file | | server |
| Variable | Description | Default | Services |
| :-------------------------- | :------------------------------------------- | :-----------------: | :------------------------------------------- |
| `TZ` | Timezone | | microservices |
| `NODE_ENV` | Environment (production, development) | `production` | server, microservices, machine learning, web |
| `LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices |
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload` | server, microservices |
| `PUBLIC_LOGIN_PAGE_MESSAGE` | Public Login Page Message | | web |
| `IMMICH_CONFIG_FILE` | Path to config file | | server |
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www'` | server |
:::tip
@@ -124,51 +125,6 @@ Redis (Sentinel) URL example JSON before encoding:
}
```
## Typesense
| Variable | Description | Default | Services |
| :------------------- | :----------------------- | :---------: | :------------------------------- |
| `TYPESENSE_ENABLED` | Enable Typesense | | server, microservices |
| `TYPESENSE_URL` | Typesense URL | | server, microservices |
| `TYPESENSE_HOST` | Typesense Host | `typesense` | server, microservices |
| `TYPESENSE_PORT` | Typesense Port | `8108` | server, microservices |
| `TYPESENSE_PROTOCOL` | Typesense Protocol | `http` | server, microservices |
| `TYPESENSE_API_KEY` | Typesense API Key | | server, microservices, typesense |
| `TYPESENSE_DATA_DIR` | Typesense Data Directory | `/data` | typesense |
:::info
`TYPESENSE_URL` must start with `ha://` and then include a `base64` encoded JSON string for the configuration.
`TYPESENSE_ENABLED`: Anything other than `false`, behaves as `true`.
Even undefined is treated as `true`.
- When `TYPESENSE_URL` is defined, the other typesense (`TYPESENSE_*`) variables are ignored.
:::
Typesense URL example JSON before encoding:
```json
[
{
"host": "typesense-1.example.net",
"port": "443",
"protocol": "https"
},
{
"host": "typesense-2.example.net",
"port": "443",
"protocol": "https"
},
{
"host": "typesense-3.example.net",
"port": "443",
"protocol": "https"
}
]
```
## Machine Learning
| Variable | Description | Default | Services |

View File

@@ -18,7 +18,4 @@ search home.lan
nameserver 192.168.1.1
```
When you encounter this bug, it will cause the immich-microservices to crash on startup because it cannot download
the geocoder data. This can be solved in one of two ways: Either reconfigure your nodes to remove the searchdomain from
`resolv.conf`, or set the `DISABLE_REVERSE_GEOCODING` environment variable for Immich to `true` to disable the geocoder.
:::

View File

@@ -5,7 +5,7 @@ sidebar_position: 20
# Install Script [Experimental]
:::caution
This method is experimental and not currently recommended for production use. For production, please refer to installing with [Docker Compose](/docs/install/docker-compose.md).
This method is experimental and not currently recommended for production use. For production, please refer to installing with [Docker Compose](/docs/install/docker-compose.mdx).
:::
In the shell, from a directory of your choice, run the following command:

View File

@@ -24,7 +24,7 @@ There are lots of non-monetary ways to contribute to Immich as well.
1. Testing - Using Immich and reporting bugs is a great way to help support the project. Found a bug? [Open an issue on GitHub][github-issue].
1. Translations - The Immich mobile app has been translated into [17 languages][github-langs] so far! To contribute with translations, email me at alex.tran1502@gmail.com or send me a message on discord.
1. Development - If you are a programmer or developer, take a look at Immich's [technology stack](/docs/developer/architecture.md) and consider fixing bugs or building new features. The team and I are always looking for new contributors. For information about how to contribute as a developer, see the [Developer](/docs/developer/architecture.md) section.
1. Development - If you are a programmer or developer, take a look at Immich's [technology stack](/docs/developer/architecture.mdx) and consider fixing bugs or building new features. The team and I are always looking for new contributors. For information about how to contribute as a developer, see the [Developer](/docs/developer/architecture.mdx) section.
[github-issue]: https://github.com/immich-app/immich/issues/new/choose
[github-langs]: https://github.com/immich-app/immich/tree/main/mobile/assets/i18n

View File

@@ -8,7 +8,7 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const config = {
title: 'Immich',
tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
url: 'https://documentation.immich.app',
url: 'https://immich.app',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',

84
docs/package-lock.json generated
View File

@@ -20,6 +20,7 @@
"docusaurus-preset-openapi": "^0.6.3",
"postcss": "^8.4.25",
"prism-react-renderer": "^1.3.5",
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"tailwindcss": "^3.2.4",
@@ -11094,6 +11095,57 @@
"node": ">=0.10.0"
}
},
"node_modules/raw-loader": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
"dependencies": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/raw-loader/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/raw-loader/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -22929,6 +22981,38 @@
}
}
},
"raw-loader": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
"requires": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"requires": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
}
}
}
},
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",

View File

@@ -29,6 +29,7 @@
"docusaurus-preset-openapi": "^0.6.3",
"postcss": "^8.4.25",
"prism-react-renderer": "^1.3.5",
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"tailwindcss": "^3.2.4",

View File

@@ -32,6 +32,15 @@ function HomepageHeader() {
>
Demo portal
</Link>
<a
href="https://github.com/sponsors/immich-app"
target="_blank"
rel="noreferrer"
className="flex place-items-center place-content-center py-3 px-8 border bg-immich-sponsor rounded-full no-underline hover:no-underline text-white dark:text-immich-dark-bg dark:bg-immich-sponsor hover:text-white font-bold uppercase"
>
Sponsor
</a>
</div>
<img src="/img/immich-screenshots.png" alt="screenshots" width={'85%'} />

View File

@@ -26,12 +26,14 @@ import {
mdiMagnify,
mdiMap,
mdiMaterialDesign,
mdiMatrix,
mdiMerge,
mdiMonitor,
mdiMotionPlayOutline,
mdiPalette,
mdiPanVertical,
mdiPartyPopper,
mdiPencil,
mdiRaw,
mdiRotate360,
mdiSecurity,
@@ -52,6 +54,24 @@ import React from 'react';
import Timeline, { DateType, Item } from '../components/timeline';
const items: Item[] = [
{
icon: mdiMatrix,
description: 'Moved the search from typesense to pgvecto.rs',
title: 'Search improvement with pgvecto.rs',
release: 'v1.91.0',
tag: 'v1.91.0',
date: new Date(2023, 11, 15),
dateType: DateType.RELEASE,
},
{
icon: mdiPencil,
description: "Edit a photo or video's date, time, hours, timezone, and GPS information",
title: 'Edit metadata',
release: 'v1.90.0',
tag: 'v1.90.0',
date: new Date(2023, 11, 7),
dateType: DateType.RELEASE,
},
{
icon: mdiVectorCombine,
description:

25
docs/static/_redirects vendored Normal file
View File

@@ -0,0 +1,25 @@
/docs /docs/overview/introduction 301
/docs/mobile-app-beta-program /docs/features/mobile-app 301
/docs/contribution-guidelines /docs/overview/support-the-project#contributing 301
/docs/install /docs/install/docker-compose 301
/docs/installation/one-step-installation /docs/install/script 301
/docs/installation/portainer-installation /docs/install/portainer 301
/docs/installation/recommended-installation /docs/install/docker-compose 301
/docs/installation/unraid /docs/install/unraid 301
/docs/installation/requirements /docs/install/requirements 301
/docs/overview/logo-meaning /docs/overview/logo 301
/docs/overview/technology-stack /docs/developer/architecture 301
/docs/usage/automatic-backup /docs/features/automatic-backup 301
/docs/usage/bulk-upload /docs/features/command-line-interface 301
/docs/features/bulk-upload /docs/features/command-line-interface 301
/docs/usage/oauth /docs/administration/oauth 301
/docs/usage/post-installation /docs/install/post-install 301
/docs/usage/update /docs/install/docker-compose#step-4---upgrading 301
/docs/usage/server-commands /docs/administration/server-commands 301
/docs/features/jobs /docs/administration/jobs 301
/docs/features/oauth /docs/administration/oauth 301
/docs/features/password-login /docs/administration/password-login 301
/docs/features/server-commands /docs/administration/server-commands 301
/docs/features/storage-template /docs/administration/storage-template 301
/docs/features/user-management /docs/administration/user-management 301
/docs/developer/contributing /docs/developer/pr-checklist 301

View File

@@ -20,6 +20,8 @@ module.exports = {
'immich-dark-bg': 'black',
'immich-dark-fg': '#e5e7eb',
'immich-dark-gray': '#212121',
'immich-sponsor': '#db61a2',
},
fontFamily: {
'immich-title': ['Snowburst One', 'cursive'],

View File

@@ -1,29 +0,0 @@
{
"redirects": [
{ "source": "/docs", "destination": "/docs/overview/introduction" },
{ "source": "/docs/mobile-app-beta-program", "destination": "/docs/features/mobile-app" },
{ "source": "/docs/contribution-guidelines", "destination": "/docs/overview/support-the-project#contributing" },
{ "source": "/docs/install", "destination": "/docs/install/docker-compose" },
{ "source": "/docs/installation/one-step-installation", "destination": "/docs/install/script" },
{ "source": "/docs/installation/portainer-installation", "destination": "/docs/install/portainer" },
{ "source": "/docs/installation/recommended-installation", "destination": "/docs/install/docker-compose" },
{ "source": "/docs/installation/unraid", "destination": "/docs/install/unraid" },
{ "source": "/docs/installation/requirements", "destination": "/docs/install/requirements" },
{ "source": "/docs/overview/logo-meaning", "destination": "/docs/overview/logo" },
{ "source": "/docs/overview/technology-stack", "destination": "/docs/developer/architecture" },
{ "source": "/docs/usage/automatic-backup", "destination": "/docs/features/automatic-backup" },
{ "source": "/docs/usage/bulk-upload", "destination": "/docs/features/command-line-interface" },
{ "source": "/docs/features/bulk-upload", "destination": "/docs/features/command-line-interface" },
{ "source": "/docs/usage/oauth", "destination": "/docs/administration/oauth" },
{ "source": "/docs/usage/post-installation", "destination": "/docs/install/post-install" },
{ "source": "/docs/usage/update", "destination": "/docs/install/docker-compose#step-4---upgrading" },
{ "source": "/docs/usage/server-commands", "destination": "/docs/administration/server-commands" },
{ "source": "/docs/features/jobs", "destination": "/docs/administration/jobs" },
{ "source": "/docs/features/oauth", "destination": "/docs/administration/oauth" },
{ "source": "/docs/features/password-login", "destination": "/docs/administration/password-login" },
{ "source": "/docs/features/server-commands", "destination": "/docs/administration/server-commands" },
{ "source": "/docs/features/storage-template", "destination": "/docs/administration/storage-template" },
{ "source": "/docs/features/user-management", "destination": "/docs/administration/user-management" },
{ "source": "/docs/developer/contributing", "destination": "/docs/developer/pr-checklist" }
]
}

View File

@@ -13,7 +13,7 @@ ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
COPY poetry.lock pyproject.toml ./
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
FROM python:3.11-slim-bookworm@sha256:cc758519481092eb5a4a5ab0c1b303e288880d59afc601958d19e95b300bc86b
FROM python:3.11-slim-bookworm@sha256:cfd7ed5c11a88ce533d69a1da2fd932d647f9eb6791c5b4ddce081aedf7f7876
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*

View File

@@ -1,12 +1,16 @@
import logging
import os
import sys
from pathlib import Path
from socket import socket
import gunicorn
import starlette
from gunicorn.arbiter import Arbiter
from pydantic import BaseSettings
from rich.console import Console
from rich.logging import RichHandler
from uvicorn import Server
from uvicorn.workers import UvicornWorker
from .schemas import ModelType
@@ -69,10 +73,26 @@ log_settings = LogSettings()
class CustomRichHandler(RichHandler):
def __init__(self) -> None:
console = Console(color_system="standard", no_color=log_settings.no_color)
super().__init__(
show_path=False, omit_repeated_times=False, console=console, tracebacks_suppress=[gunicorn, starlette]
)
super().__init__(show_path=False, omit_repeated_times=False, console=console, tracebacks_suppress=[starlette])
log = logging.getLogger("gunicorn.access")
log.setLevel(LOG_LEVELS.get(log_settings.log_level.lower(), logging.INFO))
# patches this issue https://github.com/encode/uvicorn/discussions/1803
class CustomUvicornServer(Server):
async def shutdown(self, sockets: list[socket] | None = None) -> None:
for sock in sockets or []:
sock.close()
await super().shutdown()
class CustomUvicornWorker(UvicornWorker):
async def _serve(self) -> None:
self.config.app = self.wsgi
server = CustomUvicornServer(config=self.config)
self._install_sigquit_handler()
await server.serve(sockets=self.sockets)
if not server.started:
sys.exit(Arbiter.WORKER_BOOT_ERROR)

View File

@@ -1,5 +1,4 @@
import json
from pathlib import Path
from typing import Any, Iterator
from unittest import mock
@@ -8,7 +7,7 @@ import pytest
from fastapi.testclient import TestClient
from PIL import Image
from .main import app, init_state
from .main import app
from .schemas import ndarray_f32
@@ -29,9 +28,9 @@ def mock_get_model() -> Iterator[mock.Mock]:
@pytest.fixture(scope="session")
def deployed_app() -> TestClient:
init_state()
return TestClient(app)
def deployed_app() -> Iterator[TestClient]:
with TestClient(app) as client:
yield client
@pytest.fixture(scope="session")

View File

@@ -1,15 +1,16 @@
import asyncio
import gc
import os
import signal
import sys
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from typing import Any
from typing import Any, Iterator
from zipfile import BadZipFile
import orjson
from fastapi import FastAPI, Form, HTTPException, UploadFile
from fastapi import Depends, FastAPI, Form, HTTPException, UploadFile
from fastapi.responses import ORJSONResponse
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile
from starlette.formparsers import MultiPartParser
@@ -27,9 +28,16 @@ from .schemas import (
MultiPartParser.max_file_size = 2**26 # spools to disk if payload is 64 MiB or larger
app = FastAPI()
model_cache = ModelCache(ttl=settings.model_ttl, revalidate=settings.model_ttl > 0)
thread_pool: ThreadPoolExecutor | None = None
lock = threading.Lock()
active_requests = 0
last_called: float | None = None
def init_state() -> None:
app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=settings.model_ttl > 0)
@app.on_event("startup")
def startup() -> None:
global thread_pool
log.info(
(
"Created in-memory cache with unloading "
@@ -37,17 +45,30 @@ def init_state() -> None:
)
)
# asyncio is a huge bottleneck for performance, so we use a thread pool to run blocking code
app.state.thread_pool = ThreadPoolExecutor(settings.request_threads) if settings.request_threads > 0 else None
app.state.lock = threading.Lock()
app.state.last_called = None
thread_pool = ThreadPoolExecutor(settings.request_threads) if settings.request_threads > 0 else None
if settings.model_ttl > 0 and settings.model_ttl_poll_s > 0:
asyncio.ensure_future(idle_shutdown_task())
log.info(f"Initialized request thread pool with {settings.request_threads} threads.")
@app.on_event("startup")
async def startup_event() -> None:
init_state()
@app.on_event("shutdown")
def shutdown() -> None:
log.handlers.clear()
for model in model_cache.cache._cache.values():
del model
if thread_pool is not None:
thread_pool.shutdown()
gc.collect()
def update_state() -> Iterator[None]:
global active_requests, last_called
active_requests += 1
last_called = time.time()
try:
yield
finally:
active_requests -= 1
@app.get("/", response_model=MessageResponse)
@@ -60,7 +81,7 @@ def ping() -> str:
return "pong"
@app.post("/predict")
@app.post("/predict", dependencies=[Depends(update_state)])
async def predict(
model_name: str = Form(alias="modelName"),
model_type: ModelType = Form(alias="modelType"),
@@ -79,17 +100,16 @@ async def predict(
except orjson.JSONDecodeError:
raise HTTPException(400, f"Invalid options JSON: {options}")
model = await load(await app.state.model_cache.get(model_name, model_type, **kwargs))
model = await load(await model_cache.get(model_name, model_type, **kwargs))
model.configure(**kwargs)
outputs = await run(model, inputs)
return ORJSONResponse(outputs)
async def run(model: InferenceModel, inputs: Any) -> Any:
app.state.last_called = time.time()
if app.state.thread_pool is None:
if thread_pool is None:
return model.predict(inputs)
return await asyncio.get_running_loop().run_in_executor(app.state.thread_pool, model.predict, inputs)
return await asyncio.get_running_loop().run_in_executor(thread_pool, model.predict, inputs)
async def load(model: InferenceModel) -> InferenceModel:
@@ -97,15 +117,15 @@ async def load(model: InferenceModel) -> InferenceModel:
return model
def _load() -> None:
with app.state.lock:
with lock:
model.load()
loop = asyncio.get_running_loop()
try:
if app.state.thread_pool is None:
if thread_pool is None:
model.load()
else:
await loop.run_in_executor(app.state.thread_pool, _load)
await loop.run_in_executor(thread_pool, _load)
return model
except (OSError, InvalidProtobuf, BadZipFile, NoSuchFile):
log.warn(
@@ -115,32 +135,23 @@ async def load(model: InferenceModel) -> InferenceModel:
)
)
model.clear_cache()
if app.state.thread_pool is None:
if thread_pool is None:
model.load()
else:
await loop.run_in_executor(app.state.thread_pool, _load)
await loop.run_in_executor(thread_pool, _load)
return model
async def idle_shutdown_task() -> None:
while True:
log.debug("Checking for inactivity...")
if app.state.last_called is not None and time.time() - app.state.last_called > settings.model_ttl:
if (
last_called is not None
and not active_requests
and not lock.locked()
and time.time() - last_called > settings.model_ttl
):
log.info("Shutting down due to inactivity.")
loop = asyncio.get_running_loop()
for task in asyncio.all_tasks(loop):
if task is not asyncio.current_task():
try:
task.cancel()
except asyncio.CancelledError:
pass
sys.stderr.close()
sys.stdout.close()
sys.stdout = sys.stderr = open(os.devnull, "w")
try:
await app.state.model_cache.cache.clear()
gc.collect()
loop.stop()
except asyncio.CancelledError:
pass
os.kill(os.getpid(), signal.SIGINT)
break
await asyncio.sleep(settings.model_ttl_poll_s)

View File

@@ -26,6 +26,9 @@ _OPENCLIP_MODELS = {
"ViT-L-14-336__openai",
"ViT-H-14__laion2b-s32b-b79k",
"ViT-g-14__laion2b-s12b-b42k",
"ViT-L-14-quickgelu__dfn2b",
"ViT-H-14-quickgelu__dfn5b",
"ViT-H-14-378-quickgelu__dfn5b",
}
@@ -34,6 +37,9 @@ _MCLIP_MODELS = {
"XLM-Roberta-Large-Vit-B-32",
"XLM-Roberta-Large-Vit-B-16Plus",
"XLM-Roberta-Large-Vit-L-14",
"XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k",
"nllb-clip-base-siglip__v1",
"nllb-clip-large-siglip__v1",
}

View File

@@ -1,4 +1,4 @@
FROM mambaorg/micromamba:bookworm-slim@sha256:e296d47be09fc5d260eba9b191f60496f028a4f3ec41e8a14d48c0bae2c60244 as builder
FROM mambaorg/micromamba:bookworm-slim@sha256:5ea70d22075f7209d0410e28b7ce5b1703623099fa04d1154081156b180f739c as builder
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \

View File

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

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env sh
export LD_PRELOAD="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
export LD_BIND_NOW=1
: "${MACHINE_LEARNING_HOST:=0.0.0.0}"
: "${MACHINE_LEARNING_PORT:=3003}"
@@ -8,8 +9,9 @@ export LD_PRELOAD="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
: "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}"
gunicorn app.main:app \
-k uvicorn.workers.UvicornWorker \
-k app.config.CustomUvicornWorker \
-w $MACHINE_LEARNING_WORKERS \
-b $MACHINE_LEARNING_HOST:$MACHINE_LEARNING_PORT \
-t $MACHINE_LEARNING_WORKER_TIMEOUT \
--log-config-json log_conf.json
--log-config-json log_conf.json \
--graceful-timeout 0

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 114,
"android.injected.version.name" => "1.90.2",
"android.injected.version.code" => 115,
"android.injected.version.name" => "1.91.1",
}
)
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.000235">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000217">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="27.74518">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="66.694734">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="25.612783">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="27.6926">
</testcase>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 KiB

View File

@@ -379,7 +379,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
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 = 130;
CURRENT_PROJECT_VERSION = 131;
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 = 130;
CURRENT_PROJECT_VERSION = 131;
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.90.0</string>
<string>1.91.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>130</string>
<string>131</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.2"
version_number: "1.91.1"
)
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.000234">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000273">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.207521">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.162117">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="18.516191">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="3.645923">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.23018">
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.158953">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="104.984834">
<testcase classname="fastlane.lanes" name="4: build_app" time="114.023733">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="61.879749">
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="97.572612">
</testcase>

View File

@@ -99,7 +99,7 @@ class AlbumSortByOptions extends _$AlbumSortByOptions {
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.selectedAlbumSortOrder);
return AlbumSortMode.values.firstWhere(
(e) => e.index == sortOpt,
(e) => e.storeIndex == sortOpt,
orElse: () => AlbumSortMode.title,
);
}

View File

@@ -19,9 +19,11 @@ class ImageViewerService {
ImageViewerService(this._apiService);
Future<bool> downloadAssetToDevice(Asset asset) async {
File? imageFile;
File? videoFile;
try {
// Download LivePhotos image and motion part
if (asset.isImage && asset.livePhotoVideoId != null) {
if (asset.isImage && asset.livePhotoVideoId != null && Platform.isIOS) {
var imageResponse = await _apiService.assetApi.downloadFileWithHttpInfo(
asset.remoteId!,
);
@@ -40,11 +42,11 @@ class ImageViewerService {
return false;
}
final AssetEntity? entity;
AssetEntity? entity;
final tempDir = await getTemporaryDirectory();
File videoFile = await File('${tempDir.path}/livephoto.mov').create();
File imageFile = await File('${tempDir.path}/livephoto.heic').create();
videoFile = await File('${tempDir.path}/livephoto.mov').create();
imageFile = await File('${tempDir.path}/livephoto.heic').create();
videoFile.writeAsBytesSync(motionReponse.bodyBytes);
imageFile.writeAsBytesSync(imageResponse.bodyBytes);
@@ -54,6 +56,17 @@ class ImageViewerService {
title: asset.fileName,
);
if (entity == null) {
_log.warning(
"Asset cannot be saved as a live photo. This is most likely a motion photo. Saving only the image file",
);
entity = await PhotoManager.editor.saveImage(
imageResponse.bodyBytes,
title: asset.fileName,
);
}
return entity != null;
} else {
var res = await _apiService.assetApi
@@ -75,17 +88,20 @@ class ImageViewerService {
);
} else {
final tempDir = await getTemporaryDirectory();
File tempFile =
await File('${tempDir.path}/${asset.fileName}').create();
tempFile.writeAsBytesSync(res.bodyBytes);
videoFile = await File('${tempDir.path}/${asset.fileName}').create();
videoFile.writeAsBytesSync(res.bodyBytes);
entity = await PhotoManager.editor
.saveVideo(tempFile, title: asset.fileName);
.saveVideo(videoFile, title: asset.fileName);
}
return entity != null;
}
} catch (error, stack) {
_log.severe("Error saving file ${error.toString()}", error, stack);
return false;
} finally {
// Clear temp files
imageFile?.delete();
videoFile?.delete();
}
}
}

View File

@@ -453,7 +453,7 @@ class BackgroundService {
);
_cancellationToken = CancellationToken();
final pmProgressHandler = PMProgressHandler();
final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null;
final bool ok = await backupService.backupAsset(
toUpload,

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cancellation_token_http/http.dart';
import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart';
@@ -253,7 +255,6 @@ class BackupNotifier extends StateNotifier<BackUpState> {
albumMap[album.id] = album;
}
}
state = state.copyWith(availableAlbums: availableAlbums);
final List<BackupAlbum> excludedBackupAlbums =
@@ -293,6 +294,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
excludedBackupAlbums: excludedAlbums,
);
log.info(
"_getBackupAlbumsInfo: Found ${availableAlbums.length} available albums",
);
debugPrint("_getBackupAlbumsInfo takes ${stopwatch.elapsedMilliseconds}ms");
}
@@ -343,7 +347,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
);
if (allUniqueAssets.isEmpty) {
log.info("Not found albums or assets on the device to backup");
log.info("No assets are selected for back up");
state = state.copyWith(
backupProgress: BackUpProgressEnum.idle,
allAssetsInDatabase: allAssetsInDatabase,
@@ -447,9 +451,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
// Perform Backup
state = state.copyWith(cancelToken: CancellationToken());
final pmProgressHandler = PMProgressHandler();
final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null;
pmProgressHandler.stream.listen((event) {
pmProgressHandler?.stream.listen((event) {
final double progress = event.progress;
state = state.copyWith(iCloudDownloadProgress: progress);
});

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cancellation_token_http/http.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/widgets.dart';
@@ -208,7 +210,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
state.totalAssetsToUpload == 1;
state =
state.copyWith(showDetailedNotification: showDetailedNotification);
final pmProgressHandler = PMProgressHandler();
final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null;
final bool ok = await ref.read(backupServiceProvider).backupAsset(
allUploadAssets,

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:cancellation_token_http/http.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -206,7 +205,7 @@ class BackupService {
Future<bool> backupAsset(
Iterable<AssetEntity> assetList,
http.CancellationToken cancelToken,
PMProgressHandler pmProgressHandler,
PMProgressHandler? pmProgressHandler,
Function(String, String, bool) uploadSuccessCb,
Function(int, int) uploadProgressCb,
Function(CurrentUploadAsset) setCurrentUploadAssetCb,
@@ -226,7 +225,6 @@ class BackupService {
}
final String deviceId = Store.get(StoreKey.deviceId);
final String savedEndpoint = Store.get(StoreKey.serverEndpoint);
File? file;
bool anyErrors = false;
final List<String> duplicatedAssetIds = [];
@@ -248,8 +246,12 @@ class BackupService {
: assetList.toList();
for (var entity in assetsToUpload) {
File? file;
File? livePhotoFile;
try {
final isAvailableLocally = await entity.isLocallyAvailable();
final isAvailableLocally =
await entity.isLocallyAvailable(isOrigin: true);
// Handle getting files from iCloud
if (!isAvailableLocally && Platform.isIOS) {
@@ -271,11 +273,19 @@ class BackupService {
);
file = await entity.loadFile(progressHandler: pmProgressHandler);
livePhotoFile = await entity.loadFile(
withSubtype: true,
progressHandler: pmProgressHandler,
);
} else {
if (entity.type == AssetType.video) {
file = await entity.originFile;
} else {
file = await entity.originFile.timeout(const Duration(seconds: 5));
if (entity.isLivePhoto) {
livePhotoFile = await entity.originFileWithSubtype
.timeout(const Duration(seconds: 5));
}
}
}
@@ -310,6 +320,27 @@ class BackupService {
req.files.add(assetRawUploadData);
if (entity.isLivePhoto) {
if (livePhotoFile != null) {
final livePhotoTitle = p.setExtension(
originalFileName,
p.extension(livePhotoFile.path),
);
final fileStream = livePhotoFile.openRead();
final livePhotoRawUploadData = http.MultipartFile(
"livePhotoData",
fileStream,
livePhotoFile.lengthSync(),
filename: livePhotoTitle,
);
req.files.add(livePhotoRawUploadData);
} else {
_log.warning(
"Failed to obtain motion part of the livePhoto - $originalFileName",
);
}
}
setCurrentUploadAssetCb(
CurrentUploadAsset(
id: entity.id,
@@ -325,29 +356,6 @@ class BackupService {
var response =
await httpClient.send(req, cancellationToken: cancelToken);
// Send live photo separately
if (entity.isLivePhoto) {
var livePhotoRawUploadData = await _getLivePhotoFile(entity);
if (livePhotoRawUploadData != null) {
var livePhotoReq = MultipartRequest(
req.method,
req.url,
onProgress: req.onProgress,
)
..headers.addAll(req.headers)
..fields.addAll(req.fields);
livePhotoReq.files.add(livePhotoRawUploadData);
// Send live photo only if the non-motion part is successful
if (response.statusCode == 200 || response.statusCode == 201) {
response = await httpClient.send(
livePhotoReq,
cancellationToken: cancelToken,
);
}
}
}
if (response.statusCode == 200) {
// asset is a duplicate (already exists on the server)
duplicatedAssetIds.add(entity.id);
@@ -387,6 +395,7 @@ class BackupService {
} finally {
if (Platform.isIOS) {
file?.deleteSync();
livePhotoFile?.deleteSync();
}
}
}
@@ -396,25 +405,6 @@ class BackupService {
return !anyErrors;
}
Future<MultipartFile?> _getLivePhotoFile(AssetEntity entity) async {
var motionFilePath = await entity.getMediaUrl();
if (motionFilePath != null) {
var validPath = motionFilePath.replaceAll('file://', '');
var motionFile = File(validPath);
var fileStream = motionFile.openRead();
String fileName = p.basename(motionFile.path);
return http.MultipartFile(
"assetData",
fileStream,
motionFile.lengthSync(),
filename: fileName,
);
}
return null;
}
String _getAssetType(AssetType assetType) {
switch (assetType) {
case AssetType.audio:

View File

@@ -17,12 +17,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums;
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
final isDarkTheme = context.isDarkTheme;
final allAlbums = ref.watch(backupProvider).availableAlbums;
// Albums which are displayed to the user
// by filtering out based on search
final filteredAlbums = useState(allAlbums);
final albums = filteredAlbums.value;
final albums = ref.watch(backupProvider).availableAlbums;
useEffect(
() {
@@ -153,47 +148,47 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
}).toSet();
}
buildSearchBar() {
return Padding(
padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 8.0),
child: TextFormField(
onChanged: (searchValue) {
if (searchValue.isEmpty) {
filteredAlbums.value = allAlbums;
} else {
filteredAlbums.value = allAlbums
.where(
(album) => album.name
.toLowerCase()
.contains(searchValue.toLowerCase()),
)
.toList();
}
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 8.0,
),
hintText: "Search",
hintStyle: TextStyle(
color: isDarkTheme ? Colors.white : Colors.grey,
fontSize: 14.0,
),
prefixIcon: const Icon(
Icons.search,
color: Colors.grey,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
filled: true,
fillColor: isDarkTheme ? Colors.white30 : Colors.grey[200],
),
),
);
}
// buildSearchBar() {
// return Padding(
// padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 8.0),
// child: TextFormField(
// onChanged: (searchValue) {
// // if (searchValue.isEmpty) {
// // albums = availableAlbums;
// // } else {
// // albums.value = availableAlbums
// // .where(
// // (album) => album.name
// // .toLowerCase()
// // .contains(searchValue.toLowerCase()),
// // )
// // .toList();
// // }
// },
// decoration: InputDecoration(
// contentPadding: const EdgeInsets.symmetric(
// horizontal: 8.0,
// vertical: 8.0,
// ),
// hintText: "Search",
// hintStyle: TextStyle(
// color: isDarkTheme ? Colors.white : Colors.grey,
// fontSize: 14.0,
// ),
// prefixIcon: const Icon(
// Icons.search,
// color: Colors.grey,
// ),
// border: OutlineInputBorder(
// borderRadius: BorderRadius.circular(10),
// borderSide: BorderSide.none,
// ),
// filled: true,
// fillColor: isDarkTheme ? Colors.white30 : Colors.grey[200],
// ),
// ),
// );
// }
return Scaffold(
appBar: AppBar(
@@ -301,7 +296,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
),
),
buildSearchBar(),
// buildSearchBar(),
],
),
),

View File

@@ -92,18 +92,6 @@ class ControlBottomAppBar extends ConsumerWidget {
.tr(),
onPressed: enabled ? onFavorite : null,
),
if (hasRemote && onEditTime != null)
ControlBoxButton(
iconData: Icons.edit_calendar_outlined,
label: "control_bottom_app_bar_edit_time".tr(),
onPressed: enabled ? onEditTime : null,
),
if (hasRemote && onEditLocation != null)
ControlBoxButton(
iconData: Icons.edit_location_alt_outlined,
label: "control_bottom_app_bar_edit_location".tr(),
onPressed: enabled ? onEditLocation : null,
),
if (onDelete != null)
ControlBoxButton(
iconData: Icons.delete_outline_rounded,
@@ -125,6 +113,18 @@ class ControlBottomAppBar extends ConsumerWidget {
}
: null,
),
if (hasRemote && onEditTime != null)
ControlBoxButton(
iconData: Icons.edit_calendar_outlined,
label: "control_bottom_app_bar_edit_time".tr(),
onPressed: enabled ? onEditTime : null,
),
if (hasRemote && onEditLocation != null)
ControlBoxButton(
iconData: Icons.edit_location_alt_outlined,
label: "control_bottom_app_bar_edit_location".tr(),
onPressed: enabled ? onEditLocation : null,
),
if (!hasLocal &&
selectionAssetState.selectedCount > 1 &&
onStack != null)

View File

@@ -8,6 +8,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
import 'package:immich_mobile/modules/memories/ui/memory_lane.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/providers/user.provider.dart';
@@ -95,8 +96,12 @@ class HomePage extends HookConsumerWidget {
}
}
Widget buildBody() {
return MultiselectGrid(
return Scaffold(
appBar: ref.watch(multiselectProvider) ? null : const ImmichAppBar(),
body: MultiselectGrid(
topWidget: (currentUser != null && currentUser.memoryEnabled)
? const MemoryLane()
: const SizedBox(),
renderListProvider: timelineUsers.length > 1
? multiUserAssetsProvider(timelineUsers)
: assetsProvider(currentUser?.isarId),
@@ -105,12 +110,7 @@ class HomePage extends HookConsumerWidget {
stackEnabled: true,
archiveEnabled: true,
editEnabled: true,
);
}
return Scaffold(
appBar: ref.watch(multiselectProvider) ? null : const ImmichAppBar(),
body: buildBody(),
),
);
}
}

View File

@@ -44,7 +44,7 @@ class MapStateNotifier extends StateNotifier<MapState> {
state.mapStyle != null && state.mapStyle!.rasterTileProvider != null;
double get maxZoom =>
(isRaster ? state.mapStyle!.rasterTileProvider!.maximumZoom : 14)
(isRaster ? state.mapStyle!.rasterTileProvider!.maximumZoom : 18)
.toDouble();
void switchTheme(bool isDarkTheme) {

View File

@@ -42,7 +42,7 @@ class UserCircleAvatar extends ConsumerWidget {
child: user.profileImagePath.isEmpty
? textIcon
: ClipRRect(
borderRadius: BorderRadius.circular(50),
borderRadius: const BorderRadius.all(Radius.circular(50)),
child: CachedNetworkImage(
fit: BoxFit.cover,
cacheKey: user.profileImagePath,

View File

@@ -82,6 +82,7 @@ doc/LibraryApi.md
doc/LibraryResponseDto.md
doc/LibraryStatsResponseDto.md
doc/LibraryType.md
doc/LogLevel.md
doc/LoginCredentialDto.md
doc/LoginResponseDto.md
doc/LogoutResponseDto.md
@@ -142,6 +143,7 @@ doc/SystemConfigFFmpegDto.md
doc/SystemConfigJobDto.md
doc/SystemConfigLibraryDto.md
doc/SystemConfigLibraryScanDto.md
doc/SystemConfigLoggingDto.md
doc/SystemConfigMachineLearningDto.md
doc/SystemConfigMapDto.md
doc/SystemConfigNewVersionCheckDto.md
@@ -274,6 +276,7 @@ lib/model/job_status_dto.dart
lib/model/library_response_dto.dart
lib/model/library_stats_response_dto.dart
lib/model/library_type.dart
lib/model/log_level.dart
lib/model/login_credential_dto.dart
lib/model/login_response_dto.dart
lib/model/logout_response_dto.dart
@@ -327,6 +330,7 @@ lib/model/system_config_f_fmpeg_dto.dart
lib/model/system_config_job_dto.dart
lib/model/system_config_library_dto.dart
lib/model/system_config_library_scan_dto.dart
lib/model/system_config_logging_dto.dart
lib/model/system_config_machine_learning_dto.dart
lib/model/system_config_map_dto.dart
lib/model/system_config_new_version_check_dto.dart
@@ -439,6 +443,7 @@ test/library_api_test.dart
test/library_response_dto_test.dart
test/library_stats_response_dto_test.dart
test/library_type_test.dart
test/log_level_test.dart
test/login_credential_dto_test.dart
test/login_response_dto_test.dart
test/logout_response_dto_test.dart
@@ -499,6 +504,7 @@ test/system_config_f_fmpeg_dto_test.dart
test/system_config_job_dto_test.dart
test/system_config_library_dto_test.dart
test/system_config_library_scan_dto_test.dart
test/system_config_logging_dto_test.dart
test/system_config_machine_learning_dto_test.dart
test/system_config_map_dto_test.dart
test/system_config_new_version_check_dto_test.dart

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.2
- API version: 1.91.1
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements
@@ -281,6 +281,7 @@ Class | Method | HTTP request | Description
- [LibraryResponseDto](doc//LibraryResponseDto.md)
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
- [LibraryType](doc//LibraryType.md)
- [LogLevel](doc//LogLevel.md)
- [LoginCredentialDto](doc//LoginCredentialDto.md)
- [LoginResponseDto](doc//LoginResponseDto.md)
- [LogoutResponseDto](doc//LogoutResponseDto.md)
@@ -334,6 +335,7 @@ Class | Method | HTTP request | Description
- [SystemConfigJobDto](doc//SystemConfigJobDto.md)
- [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md)
- [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md)
- [SystemConfigLoggingDto](doc//SystemConfigLoggingDto.md)
- [SystemConfigMachineLearningDto](doc//SystemConfigMachineLearningDto.md)
- [SystemConfigMapDto](doc//SystemConfigMapDto.md)
- [SystemConfigNewVersionCheckDto](doc//SystemConfigNewVersionCheckDto.md)

View File

@@ -9,7 +9,6 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**backgroundTask** | [**JobStatusDto**](JobStatusDto.md) | |
**clipEncoding** | [**JobStatusDto**](JobStatusDto.md) | |
**library_** | [**JobStatusDto**](JobStatusDto.md) | |
**metadataExtraction** | [**JobStatusDto**](JobStatusDto.md) | |
**migration** | [**JobStatusDto**](JobStatusDto.md) | |
@@ -17,6 +16,7 @@ Name | Type | Description | Notes
**recognizeFaces** | [**JobStatusDto**](JobStatusDto.md) | |
**search** | [**JobStatusDto**](JobStatusDto.md) | |
**sidecar** | [**JobStatusDto**](JobStatusDto.md) | |
**smartSearch** | [**JobStatusDto**](JobStatusDto.md) | |
**storageTemplateMigration** | [**JobStatusDto**](JobStatusDto.md) | |
**thumbnailGeneration** | [**JobStatusDto**](JobStatusDto.md) | |
**videoConversion** | [**JobStatusDto**](JobStatusDto.md) | |

View File

@@ -725,7 +725,7 @@ Name | Type | Description | Notes
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: image/jpeg, image/webp
- **Accept**: application/octet-stream
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

14
mobile/openapi/doc/LogLevel.md generated Normal file
View File

@@ -0,0 +1,14 @@
# openapi.model.LogLevel
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -343,7 +343,7 @@ Name | Type | Description | Notes
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: image/jpeg
- **Accept**: application/octet-stream
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

View File

@@ -66,7 +66,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **search**
> SearchResponseDto search(q, query, clip, type, isFavorite, isArchived, exifInfoPeriodCity, exifInfoPeriodState, exifInfoPeriodCountry, exifInfoPeriodMake, exifInfoPeriodModel, exifInfoPeriodProjectionType, smartInfoPeriodObjects, smartInfoPeriodTags, recent, motion)
> SearchResponseDto search(q, query, clip, type, recent, motion)
@@ -93,21 +93,11 @@ final q = q_example; // String |
final query = query_example; // String |
final clip = true; // bool |
final type = type_example; // String |
final isFavorite = true; // bool |
final isArchived = true; // bool |
final exifInfoPeriodCity = exifInfoPeriodCity_example; // String |
final exifInfoPeriodState = exifInfoPeriodState_example; // String |
final exifInfoPeriodCountry = exifInfoPeriodCountry_example; // String |
final exifInfoPeriodMake = exifInfoPeriodMake_example; // String |
final exifInfoPeriodModel = exifInfoPeriodModel_example; // String |
final exifInfoPeriodProjectionType = exifInfoPeriodProjectionType_example; // String |
final smartInfoPeriodObjects = []; // List<String> |
final smartInfoPeriodTags = []; // List<String> |
final recent = true; // bool |
final motion = true; // bool |
try {
final result = api_instance.search(q, query, clip, type, isFavorite, isArchived, exifInfoPeriodCity, exifInfoPeriodState, exifInfoPeriodCountry, exifInfoPeriodMake, exifInfoPeriodModel, exifInfoPeriodProjectionType, smartInfoPeriodObjects, smartInfoPeriodTags, recent, motion);
final result = api_instance.search(q, query, clip, type, recent, motion);
print(result);
} catch (e) {
print('Exception when calling SearchApi->search: $e\n');
@@ -122,16 +112,6 @@ Name | Type | Description | Notes
**query** | **String**| | [optional]
**clip** | **bool**| | [optional]
**type** | **String**| | [optional]
**isFavorite** | **bool**| | [optional]
**isArchived** | **bool**| | [optional]
**exifInfoPeriodCity** | **String**| | [optional]
**exifInfoPeriodState** | **String**| | [optional]
**exifInfoPeriodCountry** | **String**| | [optional]
**exifInfoPeriodMake** | **String**| | [optional]
**exifInfoPeriodModel** | **String**| | [optional]
**exifInfoPeriodProjectionType** | **String**| | [optional]
**smartInfoPeriodObjects** | [**List<String>**](String.md)| | [optional] [default to const []]
**smartInfoPeriodTags** | [**List<String>**](String.md)| | [optional] [default to const []]
**recent** | **bool**| | [optional]
**motion** | **bool**| | [optional]

View File

@@ -11,6 +11,7 @@ Name | Type | Description | Notes
**ffmpeg** | [**SystemConfigFFmpegDto**](SystemConfigFFmpegDto.md) | |
**job** | [**SystemConfigJobDto**](SystemConfigJobDto.md) | |
**library_** | [**SystemConfigLibraryDto**](SystemConfigLibraryDto.md) | |
**logging** | [**SystemConfigLoggingDto**](SystemConfigLoggingDto.md) | |
**machineLearning** | [**SystemConfigMachineLearningDto**](SystemConfigMachineLearningDto.md) | |
**map** | [**SystemConfigMapDto**](SystemConfigMapDto.md) | |
**newVersionCheck** | [**SystemConfigNewVersionCheckDto**](SystemConfigNewVersionCheckDto.md) | |

View File

@@ -9,7 +9,6 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**backgroundTask** | [**JobSettingsDto**](JobSettingsDto.md) | |
**clipEncoding** | [**JobSettingsDto**](JobSettingsDto.md) | |
**library_** | [**JobSettingsDto**](JobSettingsDto.md) | |
**metadataExtraction** | [**JobSettingsDto**](JobSettingsDto.md) | |
**migration** | [**JobSettingsDto**](JobSettingsDto.md) | |
@@ -17,6 +16,7 @@ Name | Type | Description | Notes
**recognizeFaces** | [**JobSettingsDto**](JobSettingsDto.md) | |
**search** | [**JobSettingsDto**](JobSettingsDto.md) | |
**sidecar** | [**JobSettingsDto**](JobSettingsDto.md) | |
**smartSearch** | [**JobSettingsDto**](JobSettingsDto.md) | |
**storageTemplateMigration** | [**JobSettingsDto**](JobSettingsDto.md) | |
**thumbnailGeneration** | [**JobSettingsDto**](JobSettingsDto.md) | |
**videoConversion** | [**JobSettingsDto**](JobSettingsDto.md) | |

View File

@@ -0,0 +1,16 @@
# openapi.model.SystemConfigLoggingDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**enabled** | **bool** | |
**level** | [**LogLevel**](LogLevel.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -343,7 +343,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getProfileImage**
> Object getProfileImage(id)
> MultipartFile getProfileImage(id)
@@ -384,7 +384,7 @@ Name | Type | Description | Notes
### Return type
[**Object**](Object.md)
[**MultipartFile**](MultipartFile.md)
### Authorization
@@ -393,7 +393,7 @@ Name | Type | Description | Notes
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
- **Accept**: application/octet-stream
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

View File

@@ -117,6 +117,7 @@ part 'model/job_status_dto.dart';
part 'model/library_response_dto.dart';
part 'model/library_stats_response_dto.dart';
part 'model/library_type.dart';
part 'model/log_level.dart';
part 'model/login_credential_dto.dart';
part 'model/login_response_dto.dart';
part 'model/logout_response_dto.dart';
@@ -170,6 +171,7 @@ part 'model/system_config_f_fmpeg_dto.dart';
part 'model/system_config_job_dto.dart';
part 'model/system_config_library_dto.dart';
part 'model/system_config_library_scan_dto.dart';
part 'model/system_config_logging_dto.dart';
part 'model/system_config_machine_learning_dto.dart';
part 'model/system_config_map_dto.dart';
part 'model/system_config_new_version_check_dto.dart';

View File

@@ -71,30 +71,10 @@ class SearchApi {
///
/// * [String] type:
///
/// * [bool] isFavorite:
///
/// * [bool] isArchived:
///
/// * [String] exifInfoPeriodCity:
///
/// * [String] exifInfoPeriodState:
///
/// * [String] exifInfoPeriodCountry:
///
/// * [String] exifInfoPeriodMake:
///
/// * [String] exifInfoPeriodModel:
///
/// * [String] exifInfoPeriodProjectionType:
///
/// * [List<String>] smartInfoPeriodObjects:
///
/// * [List<String>] smartInfoPeriodTags:
///
/// * [bool] recent:
///
/// * [bool] motion:
Future<Response> searchWithHttpInfo({ String? q, String? query, bool? clip, String? type, bool? isFavorite, bool? isArchived, String? exifInfoPeriodCity, String? exifInfoPeriodState, String? exifInfoPeriodCountry, String? exifInfoPeriodMake, String? exifInfoPeriodModel, String? exifInfoPeriodProjectionType, List<String>? smartInfoPeriodObjects, List<String>? smartInfoPeriodTags, bool? recent, bool? motion, }) async {
Future<Response> searchWithHttpInfo({ String? q, String? query, bool? clip, String? type, bool? recent, bool? motion, }) async {
// ignore: prefer_const_declarations
final path = r'/search';
@@ -117,36 +97,6 @@ class SearchApi {
if (type != null) {
queryParams.addAll(_queryParams('', 'type', type));
}
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (exifInfoPeriodCity != null) {
queryParams.addAll(_queryParams('', 'exifInfo.city', exifInfoPeriodCity));
}
if (exifInfoPeriodState != null) {
queryParams.addAll(_queryParams('', 'exifInfo.state', exifInfoPeriodState));
}
if (exifInfoPeriodCountry != null) {
queryParams.addAll(_queryParams('', 'exifInfo.country', exifInfoPeriodCountry));
}
if (exifInfoPeriodMake != null) {
queryParams.addAll(_queryParams('', 'exifInfo.make', exifInfoPeriodMake));
}
if (exifInfoPeriodModel != null) {
queryParams.addAll(_queryParams('', 'exifInfo.model', exifInfoPeriodModel));
}
if (exifInfoPeriodProjectionType != null) {
queryParams.addAll(_queryParams('', 'exifInfo.projectionType', exifInfoPeriodProjectionType));
}
if (smartInfoPeriodObjects != null) {
queryParams.addAll(_queryParams('multi', 'smartInfo.objects', smartInfoPeriodObjects));
}
if (smartInfoPeriodTags != null) {
queryParams.addAll(_queryParams('multi', 'smartInfo.tags', smartInfoPeriodTags));
}
if (recent != null) {
queryParams.addAll(_queryParams('', 'recent', recent));
}
@@ -178,31 +128,11 @@ class SearchApi {
///
/// * [String] type:
///
/// * [bool] isFavorite:
///
/// * [bool] isArchived:
///
/// * [String] exifInfoPeriodCity:
///
/// * [String] exifInfoPeriodState:
///
/// * [String] exifInfoPeriodCountry:
///
/// * [String] exifInfoPeriodMake:
///
/// * [String] exifInfoPeriodModel:
///
/// * [String] exifInfoPeriodProjectionType:
///
/// * [List<String>] smartInfoPeriodObjects:
///
/// * [List<String>] smartInfoPeriodTags:
///
/// * [bool] recent:
///
/// * [bool] motion:
Future<SearchResponseDto?> search({ String? q, String? query, bool? clip, String? type, bool? isFavorite, bool? isArchived, String? exifInfoPeriodCity, String? exifInfoPeriodState, String? exifInfoPeriodCountry, String? exifInfoPeriodMake, String? exifInfoPeriodModel, String? exifInfoPeriodProjectionType, List<String>? smartInfoPeriodObjects, List<String>? smartInfoPeriodTags, bool? recent, bool? motion, }) async {
final response = await searchWithHttpInfo( q: q, query: query, clip: clip, type: type, isFavorite: isFavorite, isArchived: isArchived, exifInfoPeriodCity: exifInfoPeriodCity, exifInfoPeriodState: exifInfoPeriodState, exifInfoPeriodCountry: exifInfoPeriodCountry, exifInfoPeriodMake: exifInfoPeriodMake, exifInfoPeriodModel: exifInfoPeriodModel, exifInfoPeriodProjectionType: exifInfoPeriodProjectionType, smartInfoPeriodObjects: smartInfoPeriodObjects, smartInfoPeriodTags: smartInfoPeriodTags, recent: recent, motion: motion, );
Future<SearchResponseDto?> search({ String? q, String? query, bool? clip, String? type, bool? recent, bool? motion, }) async {
final response = await searchWithHttpInfo( q: q, query: query, clip: clip, type: type, recent: recent, motion: motion, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -327,7 +327,7 @@ class UserApi {
/// Parameters:
///
/// * [String] id (required):
Future<Object?> getProfileImage(String id,) async {
Future<MultipartFile?> getProfileImage(String id,) async {
final response = await getProfileImageWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -336,7 +336,7 @@ class UserApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;

View File

@@ -321,6 +321,8 @@ class ApiClient {
return LibraryStatsResponseDto.fromJson(value);
case 'LibraryType':
return LibraryTypeTypeTransformer().decode(value);
case 'LogLevel':
return LogLevelTypeTransformer().decode(value);
case 'LoginCredentialDto':
return LoginCredentialDto.fromJson(value);
case 'LoginResponseDto':
@@ -427,6 +429,8 @@ class ApiClient {
return SystemConfigLibraryDto.fromJson(value);
case 'SystemConfigLibraryScanDto':
return SystemConfigLibraryScanDto.fromJson(value);
case 'SystemConfigLoggingDto':
return SystemConfigLoggingDto.fromJson(value);
case 'SystemConfigMachineLearningDto':
return SystemConfigMachineLearningDto.fromJson(value);
case 'SystemConfigMapDto':

View File

@@ -88,6 +88,9 @@ String parameterToString(dynamic value) {
if (value is LibraryType) {
return LibraryTypeTypeTransformer().encode(value).toString();
}
if (value is LogLevel) {
return LogLevelTypeTransformer().encode(value).toString();
}
if (value is MapTheme) {
return MapThemeTypeTransformer().encode(value).toString();
}

View File

@@ -14,7 +14,6 @@ class AllJobStatusResponseDto {
/// Returns a new [AllJobStatusResponseDto] instance.
AllJobStatusResponseDto({
required this.backgroundTask,
required this.clipEncoding,
required this.library_,
required this.metadataExtraction,
required this.migration,
@@ -22,6 +21,7 @@ class AllJobStatusResponseDto {
required this.recognizeFaces,
required this.search,
required this.sidecar,
required this.smartSearch,
required this.storageTemplateMigration,
required this.thumbnailGeneration,
required this.videoConversion,
@@ -29,8 +29,6 @@ class AllJobStatusResponseDto {
JobStatusDto backgroundTask;
JobStatusDto clipEncoding;
JobStatusDto library_;
JobStatusDto metadataExtraction;
@@ -45,6 +43,8 @@ class AllJobStatusResponseDto {
JobStatusDto sidecar;
JobStatusDto smartSearch;
JobStatusDto storageTemplateMigration;
JobStatusDto thumbnailGeneration;
@@ -54,7 +54,6 @@ class AllJobStatusResponseDto {
@override
bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto &&
other.backgroundTask == backgroundTask &&
other.clipEncoding == clipEncoding &&
other.library_ == library_ &&
other.metadataExtraction == metadataExtraction &&
other.migration == migration &&
@@ -62,6 +61,7 @@ class AllJobStatusResponseDto {
other.recognizeFaces == recognizeFaces &&
other.search == search &&
other.sidecar == sidecar &&
other.smartSearch == smartSearch &&
other.storageTemplateMigration == storageTemplateMigration &&
other.thumbnailGeneration == thumbnailGeneration &&
other.videoConversion == videoConversion;
@@ -70,7 +70,6 @@ class AllJobStatusResponseDto {
int get hashCode =>
// ignore: unnecessary_parenthesis
(backgroundTask.hashCode) +
(clipEncoding.hashCode) +
(library_.hashCode) +
(metadataExtraction.hashCode) +
(migration.hashCode) +
@@ -78,17 +77,17 @@ class AllJobStatusResponseDto {
(recognizeFaces.hashCode) +
(search.hashCode) +
(sidecar.hashCode) +
(smartSearch.hashCode) +
(storageTemplateMigration.hashCode) +
(thumbnailGeneration.hashCode) +
(videoConversion.hashCode);
@override
String toString() => 'AllJobStatusResponseDto[backgroundTask=$backgroundTask, clipEncoding=$clipEncoding, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, objectTagging=$objectTagging, recognizeFaces=$recognizeFaces, search=$search, sidecar=$sidecar, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]';
String toString() => 'AllJobStatusResponseDto[backgroundTask=$backgroundTask, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, objectTagging=$objectTagging, recognizeFaces=$recognizeFaces, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'backgroundTask'] = this.backgroundTask;
json[r'clipEncoding'] = this.clipEncoding;
json[r'library'] = this.library_;
json[r'metadataExtraction'] = this.metadataExtraction;
json[r'migration'] = this.migration;
@@ -96,6 +95,7 @@ class AllJobStatusResponseDto {
json[r'recognizeFaces'] = this.recognizeFaces;
json[r'search'] = this.search;
json[r'sidecar'] = this.sidecar;
json[r'smartSearch'] = this.smartSearch;
json[r'storageTemplateMigration'] = this.storageTemplateMigration;
json[r'thumbnailGeneration'] = this.thumbnailGeneration;
json[r'videoConversion'] = this.videoConversion;
@@ -111,7 +111,6 @@ class AllJobStatusResponseDto {
return AllJobStatusResponseDto(
backgroundTask: JobStatusDto.fromJson(json[r'backgroundTask'])!,
clipEncoding: JobStatusDto.fromJson(json[r'clipEncoding'])!,
library_: JobStatusDto.fromJson(json[r'library'])!,
metadataExtraction: JobStatusDto.fromJson(json[r'metadataExtraction'])!,
migration: JobStatusDto.fromJson(json[r'migration'])!,
@@ -119,6 +118,7 @@ class AllJobStatusResponseDto {
recognizeFaces: JobStatusDto.fromJson(json[r'recognizeFaces'])!,
search: JobStatusDto.fromJson(json[r'search'])!,
sidecar: JobStatusDto.fromJson(json[r'sidecar'])!,
smartSearch: JobStatusDto.fromJson(json[r'smartSearch'])!,
storageTemplateMigration: JobStatusDto.fromJson(json[r'storageTemplateMigration'])!,
thumbnailGeneration: JobStatusDto.fromJson(json[r'thumbnailGeneration'])!,
videoConversion: JobStatusDto.fromJson(json[r'videoConversion'])!,
@@ -170,7 +170,6 @@ class AllJobStatusResponseDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'backgroundTask',
'clipEncoding',
'library',
'metadataExtraction',
'migration',
@@ -178,6 +177,7 @@ class AllJobStatusResponseDto {
'recognizeFaces',
'search',
'sidecar',
'smartSearch',
'storageTemplateMigration',
'thumbnailGeneration',
'videoConversion',

View File

@@ -28,7 +28,7 @@ class JobName {
static const videoConversion = JobName._(r'videoConversion');
static const objectTagging = JobName._(r'objectTagging');
static const recognizeFaces = JobName._(r'recognizeFaces');
static const clipEncoding = JobName._(r'clipEncoding');
static const smartSearch = JobName._(r'smartSearch');
static const backgroundTask = JobName._(r'backgroundTask');
static const storageTemplateMigration = JobName._(r'storageTemplateMigration');
static const migration = JobName._(r'migration');
@@ -43,7 +43,7 @@ class JobName {
videoConversion,
objectTagging,
recognizeFaces,
clipEncoding,
smartSearch,
backgroundTask,
storageTemplateMigration,
migration,
@@ -93,7 +93,7 @@ class JobNameTypeTransformer {
case r'videoConversion': return JobName.videoConversion;
case r'objectTagging': return JobName.objectTagging;
case r'recognizeFaces': return JobName.recognizeFaces;
case r'clipEncoding': return JobName.clipEncoding;
case r'smartSearch': return JobName.smartSearch;
case r'backgroundTask': return JobName.backgroundTask;
case r'storageTemplateMigration': return JobName.storageTemplateMigration;
case r'migration': return JobName.migration;

97
mobile/openapi/lib/model/log_level.dart generated Normal file
View File

@@ -0,0 +1,97 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class LogLevel {
/// Instantiate a new enum with the provided [value].
const LogLevel._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const verbose = LogLevel._(r'verbose');
static const debug = LogLevel._(r'debug');
static const log = LogLevel._(r'log');
static const warn = LogLevel._(r'warn');
static const error = LogLevel._(r'error');
static const fatal = LogLevel._(r'fatal');
/// List of all possible values in this [enum][LogLevel].
static const values = <LogLevel>[
verbose,
debug,
log,
warn,
error,
fatal,
];
static LogLevel? fromJson(dynamic value) => LogLevelTypeTransformer().decode(value);
static List<LogLevel>? listFromJson(dynamic json, {bool growable = false,}) {
final result = <LogLevel>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = LogLevel.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [LogLevel] to String,
/// and [decode] dynamic data back to [LogLevel].
class LogLevelTypeTransformer {
factory LogLevelTypeTransformer() => _instance ??= const LogLevelTypeTransformer._();
const LogLevelTypeTransformer._();
String encode(LogLevel data) => data.value;
/// Decodes a [dynamic value][data] to a LogLevel.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
LogLevel? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'verbose': return LogLevel.verbose;
case r'debug': return LogLevel.debug;
case r'log': return LogLevel.log;
case r'warn': return LogLevel.warn;
case r'error': return LogLevel.error;
case r'fatal': return LogLevel.fatal;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [LogLevelTypeTransformer] instance.
static LogLevelTypeTransformer? _instance;
}

Some files were not shown because too many files have changed in this diff Show More