Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aae9696f6 | ||
|
|
2f95cb89c1 | ||
|
|
cb1201e690 | ||
|
|
a2deba4734 | ||
|
|
ae2608e31d | ||
|
|
d8756f3897 | ||
|
|
7839be3b49 | ||
|
|
94e11d52dc | ||
|
|
05a1283500 | ||
|
|
f8519d60c7 | ||
|
|
899c71f297 | ||
|
|
2aa5f55cbf | ||
|
|
e9a8daa924 | ||
|
|
c2cda5f3b0 | ||
|
|
e29b80845b | ||
|
|
6c3bfc6f0f | ||
|
|
9cc904fb2a | ||
|
|
3e54ee5052 | ||
|
|
458257847e | ||
|
|
16f385626e | ||
|
|
e05c7f5b76 | ||
|
|
5c8eaa6859 | ||
|
|
4c5397d7e6 | ||
|
|
502495883d | ||
|
|
2836b8cda9 | ||
|
|
e4ee224e16 | ||
|
|
d729c863c8 | ||
|
|
9768931275 | ||
|
|
f2270ad757 | ||
|
|
8e39d389b5 | ||
|
|
9bb6befc92 | ||
|
|
3a2e9b6298 | ||
|
|
a4c057ba63 | ||
|
|
679b22fada | ||
|
|
b34abf25f0 | ||
|
|
36196f2a5d | ||
|
|
f13dce7d0d | ||
|
|
e5e6fcc46d | ||
|
|
523d01068f | ||
|
|
885eba2b7c | ||
|
|
f7429c3615 | ||
|
|
ec0526dbcb | ||
|
|
7a9a9473d1 | ||
|
|
a0b2cbe123 | ||
|
|
05550647eb | ||
|
|
c7df800d27 | ||
|
|
c602eaea4a | ||
|
|
cbca69841a | ||
|
|
af7c4ae090 | ||
|
|
fba9e784fb | ||
|
|
fb4b4e5895 | ||
|
|
c8da1c07dc | ||
|
|
ed05785005 | ||
|
|
81603fddc8 | ||
|
|
ed4358741e | ||
|
|
ac2a36bd53 | ||
|
|
f6ef226b64 | ||
|
|
f798e9beed | ||
|
|
08570875eb | ||
|
|
64e985d600 | ||
|
|
e3e4fb40fd | ||
|
|
960b68b02f | ||
|
|
33529d1d9b | ||
|
|
8057c375ba | ||
|
|
d2ad01cd2f | ||
|
|
8e07b35786 | ||
|
|
b7b4483a33 | ||
|
|
3a794d7a2b | ||
|
|
68c0112aaa | ||
|
|
8847ebeef2 | ||
|
|
188cdf9367 | ||
|
|
6acd8eb4ba | ||
|
|
92b4284b5a | ||
|
|
a426ea8fbc | ||
|
|
f206cb9403 | ||
|
|
2234394aa6 | ||
|
|
8a6284569a | ||
|
|
80591ea609 | ||
|
|
2553c54b26 | ||
|
|
2f4ee622ab | ||
|
|
02d55644e5 | ||
|
|
1e99ba8167 |
29
.github/release.yml
vendored
Normal 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:
|
||||
- "*"
|
||||
2
.github/workflows/build-mobile.yml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
@@ -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
@@ -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
|
||||
|
||||
2
.github/workflows/prepare-release.yml
vendored
@@ -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
|
||||
|
||||
|
||||
4
.github/workflows/test.yml
vendored
@@ -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
@@ -11,3 +11,4 @@ coverage
|
||||
mobile/gradle.properties
|
||||
mobile/openapi/pubspec.lock
|
||||
mobile/*.jks
|
||||
mobile/libisar.dylib
|
||||
|
||||
217
cli/src/api/open-api/api.ts
generated
@@ -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));
|
||||
},
|
||||
/**
|
||||
|
||||
2
cli/src/api/open-api/base.ts
generated
@@ -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).
|
||||
|
||||
2
cli/src/api/open-api/common.ts
generated
@@ -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).
|
||||
|
||||
2
cli/src/api/open-api/configuration.ts
generated
@@ -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).
|
||||
|
||||
2
cli/src/api/open-api/index.ts
generated
@@ -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).
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
70
docs/blog/2023/2023-recap.mdx
Normal 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, here’s 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. I’ve realized that hardly a day has gone by where the team hasn’t 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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||

|
||||
<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. -->
|
||||
132
docs/docs/developer/img/app-architecture.drawio.xml
Normal 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<br>(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<br>(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<br>(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="<h1>Server</h1>" 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 <br>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="<h2><span style="background-color: initial; font-size: 12px; font-weight: normal;">Repositories</span></h2><b>"Infra"<br></b>" 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="<i style="font-size: 10px;">HTTP (OpenAPI)</i>" 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="<i style="font-size: 10px;">HTTP (OpenAPI)</i>" 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="<h2>Core<br>Services</h2><div><b>"Domain"</b></div>" 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<br>(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<br>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="<i style="font-size: 10px;">HTTP</i>" 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="<i style="font-size: 10px;">Queue</i>" 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>
|
||||
|
Before Width: | Height: | Size: 570 KiB After Width: | Height: | Size: 55 KiB |
@@ -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:
|
||||
|
||||
@@ -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' />
|
||||
|
||||
103
docs/docs/guides/external-library.md
Normal 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" />
|
||||
BIN
docs/docs/guides/img/account-settings.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/docs/guides/img/add-path-button.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
docs/docs/guides/img/add-path-field.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/docs/guides/img/administration-link.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/docs/guides/img/create-external-library-button.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
docs/docs/guides/img/edit-import-paths.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/docs/guides/img/external-path.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/docs/guides/img/job-status.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
docs/docs/guides/img/jobs-tab.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/docs/guides/img/libraries-dropdown.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
docs/docs/guides/img/path-save.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/docs/guides/img/pencil.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
docs/docs/guides/img/scan-new-library-files.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/docs/guides/img/user-avatar.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/docs/guides/img/users-tab.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
:::
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
:::
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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%'} />
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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'],
|
||||
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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/*
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 679 KiB |
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ class BackgroundService {
|
||||
);
|
||||
|
||||
_cancellationToken = CancellationToken();
|
||||
final pmProgressHandler = PMProgressHandler();
|
||||
final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null;
|
||||
|
||||
final bool ok = await backupService.backupAsset(
|
||||
toUpload,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
6
mobile/openapi/.openapi-generator/FILES
generated
@@ -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
|
||||
|
||||
4
mobile/openapi/README.md
generated
@@ -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)
|
||||
|
||||
2
mobile/openapi/doc/AllJobStatusResponseDto.md
generated
@@ -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) | |
|
||||
|
||||
2
mobile/openapi/doc/AssetApi.md
generated
@@ -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
@@ -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)
|
||||
|
||||
|
||||
2
mobile/openapi/doc/PersonApi.md
generated
@@ -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)
|
||||
|
||||
|
||||
24
mobile/openapi/doc/SearchApi.md
generated
@@ -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]
|
||||
|
||||
|
||||
1
mobile/openapi/doc/SystemConfigDto.md
generated
@@ -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) | |
|
||||
|
||||
2
mobile/openapi/doc/SystemConfigJobDto.md
generated
@@ -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) | |
|
||||
|
||||
16
mobile/openapi/doc/SystemConfigLoggingDto.md
generated
Normal 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)
|
||||
|
||||
|
||||
6
mobile/openapi/doc/UserApi.md
generated
@@ -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)
|
||||
|
||||
|
||||
2
mobile/openapi/lib/api.dart
generated
@@ -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';
|
||||
|
||||
76
mobile/openapi/lib/api/search_api.dart
generated
@@ -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));
|
||||
}
|
||||
|
||||
4
mobile/openapi/lib/api/user_api.dart
generated
@@ -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;
|
||||
|
||||
4
mobile/openapi/lib/api_client.dart
generated
@@ -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':
|
||||
|
||||
3
mobile/openapi/lib/api_helper.dart
generated
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
6
mobile/openapi/lib/model/job_name.dart
generated
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||