Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea33cd9515 | |||
| e9ceb8b017 | |||
| 94030d809f | |||
| 4db0717a08 | |||
| b6808c1675 | |||
| a2b7403978 | |||
| 1556d978ed | |||
| 48a71ac4d9 | |||
| adf620331c | |||
| c92637df80 | |||
| 3356b023c5 | |||
| a2e8b657e6 | |||
| f420befc15 | |||
| efb844c6cd | |||
| 88d4338348 | |||
| ce7bbe88f9 | |||
| d62e90424e | |||
| 0f129cae4a | |||
| 5583635947 | |||
| b7715305b3 | |||
| 6c008176c9 | |||
| 72b1d582ba | |||
| 7b1112f3e3 | |||
| 42d0fc85ca | |||
| d32b621750 | |||
| d551003311 | |||
| dd64379a4e | |||
| c1cdda0ac4 |
Submodule
+1
Submodule base-images added at d0d3ab018c
Generated
+1
-1
@@ -47,7 +47,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
# Configurations for hardware-accelerated machine learning
|
# Configurations for hardware-accelerated machine learning
|
||||||
|
|
||||||
# If using Unraid or another platform that doesn't allow multiple Compose files,
|
# If using Unraid or another platform that doesn't allow multiple Compose files,
|
||||||
# you can inline the config for a backend by copying its contents
|
# you can inline the config for a backend by copying its contents
|
||||||
# into the immich-machine-learning service in the docker-compose.yml file.
|
# into the immich-machine-learning service in the docker-compose.yml file.
|
||||||
|
|
||||||
# See https://immich.app/docs/features/ml-hardware-acceleration for info on usage.
|
# See https://immich.app/docs/features/ml-hardware-acceleration for info on usage.
|
||||||
@@ -30,7 +28,7 @@ services:
|
|||||||
|
|
||||||
openvino:
|
openvino:
|
||||||
device_cgroup_rules:
|
device_cgroup_rules:
|
||||||
- "c 189:* rmw"
|
- 'c 189:* rmw'
|
||||||
devices:
|
devices:
|
||||||
- /dev/dri:/dev/dri
|
- /dev/dri:/dev/dri
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
# Configurations for hardware-accelerated transcoding
|
# Configurations for hardware-accelerated transcoding
|
||||||
|
|
||||||
# If using Unraid or another platform that doesn't allow multiple Compose files,
|
# If using Unraid or another platform that doesn't allow multiple Compose files,
|
||||||
@@ -12,6 +10,9 @@ services:
|
|||||||
cpu: {}
|
cpu: {}
|
||||||
|
|
||||||
nvenc:
|
nvenc:
|
||||||
|
runtime: nvidia
|
||||||
|
environment:
|
||||||
|
- DISPLAY:$DISPLAY
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
reservations:
|
reservations:
|
||||||
@@ -22,6 +23,8 @@ services:
|
|||||||
- gpu
|
- gpu
|
||||||
- compute
|
- compute
|
||||||
- video
|
- video
|
||||||
|
- display
|
||||||
|
- graphics
|
||||||
|
|
||||||
quicksync:
|
quicksync:
|
||||||
devices:
|
devices:
|
||||||
|
|||||||
Generated
+3
-3
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@immich/cli": "file:../cli",
|
"@immich/cli": "file:../cli",
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
ARG DEVICE=cpu
|
ARG DEVICE=cpu
|
||||||
|
|
||||||
FROM python:3.11-bookworm@sha256:9df8be5280f00f39931fc2168309e82ddf252a7f30cda6593b7990fb0f5f59f9 as builder-cpu
|
FROM python:3.11-bookworm@sha256:96de1ea4821d73fd2c1853d1fdc3cf794ccfe2fae4c3f08579e846de51760a61 as builder-cpu
|
||||||
|
|
||||||
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as builder-openvino
|
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as builder-openvino
|
||||||
USER root
|
USER root
|
||||||
|
|||||||
Generated
+6
-6
@@ -738,13 +738,13 @@ dotenv = ["python-dotenv"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flask-cors"
|
name = "flask-cors"
|
||||||
version = "4.0.0"
|
version = "4.0.1"
|
||||||
description = "A Flask extension adding a decorator for CORS support"
|
description = "A Flask extension adding a decorator for CORS support"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"},
|
{file = "Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677"},
|
||||||
{file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"},
|
{file = "flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1374,13 +1374,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.3"
|
version = "3.1.4"
|
||||||
description = "A very fast and expressive template engine."
|
description = "A very fast and expressive template engine."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
|
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
||||||
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
|
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "machine-learning"
|
name = "machine-learning"
|
||||||
version = "1.105.0"
|
version = "1.105.1"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ GEM
|
|||||||
artifactory (3.0.17)
|
artifactory (3.0.17)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.925.0)
|
aws-partitions (1.929.0)
|
||||||
aws-sdk-core (3.194.1)
|
aws-sdk-core (3.196.1)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.8)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.80.0)
|
aws-sdk-kms (1.81.0)
|
||||||
aws-sdk-core (~> 3, >= 3.193.0)
|
aws-sdk-core (~> 3, >= 3.193.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.149.1)
|
aws-sdk-s3 (1.151.0)
|
||||||
aws-sdk-core (~> 3, >= 3.194.0)
|
aws-sdk-core (~> 3, >= 3.194.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.8)
|
||||||
@@ -155,7 +155,7 @@ GEM
|
|||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.4.0)
|
multipart-post (2.4.1)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
naturally (2.2.1)
|
naturally (2.2.1)
|
||||||
nkf (0.2.0)
|
nkf (0.2.0)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ flutter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
def kotlin_version = '1.9.23'
|
def kotlin_version = '1.9.24'
|
||||||
def kotlin_coroutines_version = '1.8.0'
|
def kotlin_coroutines_version = '1.8.0'
|
||||||
def work_version = '2.9.0'
|
def work_version = '2.9.0'
|
||||||
def concurrent_version = '1.1.0'
|
def concurrent_version = '1.1.0'
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 139,
|
"android.injected.version.code" => 140,
|
||||||
"android.injected.version.name" => "1.105.0",
|
"android.injected.version.name" => "1.105.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')
|
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.000344">
|
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000374">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="52.933903">
|
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="84.292464">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="31.026779">
|
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="33.336934">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "7.4.2" apply false
|
id "com.android.application" version "7.4.2" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.9.23" apply false
|
id "org.jetbrains.kotlin.android" version "1.9.24" apply false
|
||||||
id "org.jetbrains.kotlin.kapt" version "1.9.23" apply false
|
id "org.jetbrains.kotlin.kapt" version "1.9.24" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|||||||
@@ -391,6 +391,7 @@
|
|||||||
"setting_image_viewer_original_title": "Original laden",
|
"setting_image_viewer_original_title": "Original laden",
|
||||||
"setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur die Miniaturansicht zu verwenden.",
|
"setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur die Miniaturansicht zu verwenden.",
|
||||||
"setting_image_viewer_preview_title": "Vorschaubild laden",
|
"setting_image_viewer_preview_title": "Vorschaubild laden",
|
||||||
|
"setting_image_viewer_title": "Bilder",
|
||||||
"setting_languages_apply": "Anwenden",
|
"setting_languages_apply": "Anwenden",
|
||||||
"setting_languages_title": "Sprachen",
|
"setting_languages_title": "Sprachen",
|
||||||
"setting_notifications_notify_failures_grace_period": "Benachrichtigung über Fehler bei der Hintergrundsicherung: {}",
|
"setting_notifications_notify_failures_grace_period": "Benachrichtigung über Fehler bei der Hintergrundsicherung: {}",
|
||||||
@@ -406,6 +407,9 @@
|
|||||||
"setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)",
|
"setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)",
|
||||||
"setting_notifications_total_progress_title": "Zeige Gesamtfortschritt bei der Hintergrundsicherung",
|
"setting_notifications_total_progress_title": "Zeige Gesamtfortschritt bei der Hintergrundsicherung",
|
||||||
"setting_pages_app_bar_settings": "Einstellungen",
|
"setting_pages_app_bar_settings": "Einstellungen",
|
||||||
|
"setting_video_viewer_looping_subtitle": "Aktivieren, damit sich ein Video in der Detailansicht automatisch wiederholt.",
|
||||||
|
"setting_video_viewer_looping_title": "Wiederholen",
|
||||||
|
"setting_video_viewer_title": "Videos",
|
||||||
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
|
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
|
||||||
"share_add": "Hinzufügen",
|
"share_add": "Hinzufügen",
|
||||||
"share_add_photos": "Fotos hinzufügen",
|
"share_add_photos": "Fotos hinzufügen",
|
||||||
|
|||||||
@@ -391,6 +391,7 @@
|
|||||||
"setting_image_viewer_original_title": "Load original image",
|
"setting_image_viewer_original_title": "Load original image",
|
||||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||||
"setting_image_viewer_preview_title": "Load preview image",
|
"setting_image_viewer_preview_title": "Load preview image",
|
||||||
|
"setting_image_viewer_title": "Images",
|
||||||
"setting_languages_apply": "Apply",
|
"setting_languages_apply": "Apply",
|
||||||
"setting_languages_title": "Languages",
|
"setting_languages_title": "Languages",
|
||||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||||
@@ -406,6 +407,9 @@
|
|||||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||||
"setting_pages_app_bar_settings": "Settings",
|
"setting_pages_app_bar_settings": "Settings",
|
||||||
|
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||||
|
"setting_video_viewer_looping_title": "Looping",
|
||||||
|
"setting_video_viewer_title": "Videos",
|
||||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||||
"share_add": "Add",
|
"share_add": "Add",
|
||||||
"share_add_photos": "Add photos",
|
"share_add_photos": "Add photos",
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ GEM
|
|||||||
artifactory (3.0.17)
|
artifactory (3.0.17)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.925.0)
|
aws-partitions (1.929.0)
|
||||||
aws-sdk-core (3.194.1)
|
aws-sdk-core (3.196.1)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.8)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.80.0)
|
aws-sdk-kms (1.81.0)
|
||||||
aws-sdk-core (~> 3, >= 3.193.0)
|
aws-sdk-core (~> 3, >= 3.193.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.149.1)
|
aws-sdk-s3 (1.151.0)
|
||||||
aws-sdk-core (~> 3, >= 3.194.0)
|
aws-sdk-core (~> 3, >= 3.194.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.8)
|
||||||
@@ -155,7 +155,7 @@ GEM
|
|||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.4.0)
|
multipart-post (2.4.1)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
naturally (2.2.1)
|
naturally (2.2.1)
|
||||||
nkf (0.2.0)
|
nkf (0.2.0)
|
||||||
|
|||||||
@@ -383,7 +383,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 156;
|
CURRENT_PROJECT_VERSION = 157;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -525,7 +525,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 156;
|
CURRENT_PROJECT_VERSION = 157;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -553,7 +553,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 156;
|
CURRENT_PROJECT_VERSION = 157;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -58,11 +58,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.104.0</string>
|
<string>1.105.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>156</string>
|
<string>157</string>
|
||||||
<key>FLTEnableImpeller</key>
|
<key>FLTEnableImpeller</key>
|
||||||
<true />
|
<true />
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Beta"
|
desc "iOS Beta"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.105.0"
|
version_number: "1.105.1"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
@@ -5,32 +5,32 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000406">
|
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.020864">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="37.890919">
|
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.917777">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="7.615129">
|
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.283943">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.535245">
|
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.944748">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="4: build_app" time="213.966277">
|
<testcase classname="fastlane.lanes" name="4: build_app" time="215.971639">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="80.742201">
|
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="76.674601">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ enum StoreKey<T> {
|
|||||||
advancedTroubleshooting<bool>(114, type: bool),
|
advancedTroubleshooting<bool>(114, type: bool),
|
||||||
logLevel<int>(115, type: int),
|
logLevel<int>(115, type: int),
|
||||||
preferRemoteImage<bool>(116, type: bool),
|
preferRemoteImage<bool>(116, type: bool),
|
||||||
|
loopVideo<bool>(117, type: bool),
|
||||||
// map related settings
|
// map related settings
|
||||||
mapShowFavoriteOnly<bool>(118, type: bool),
|
mapShowFavoriteOnly<bool>(118, type: bool),
|
||||||
mapRelativeDate<int>(119, type: int),
|
mapRelativeDate<int>(119, type: int),
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
title: const Text(
|
title: const Text(
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
|
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart';
|
import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart';
|
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
|
import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
|
||||||
|
import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class BackupControllerPage extends HookConsumerWidget {
|
class BackupControllerPage extends HookConsumerWidget {
|
||||||
@@ -260,7 +260,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref.watch(websocketProvider.notifier).listenUploadEvent();
|
ref.watch(websocketProvider.notifier).listenUploadEvent();
|
||||||
context.popRoute(true);
|
context.maybePop(true);
|
||||||
},
|
},
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class BackupOptionsPage extends StatelessWidget {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
title: const Text("backup_options_page_title").tr(),
|
title: const Text("backup_options_page_title").tr(),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(true),
|
onPressed: () => context.maybePop(true),
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.arrow_back_ios_rounded,
|
Icons.arrow_back_ios_rounded,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class FailedBackupStatusPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.popRoute(true);
|
context.maybePop(true);
|
||||||
},
|
},
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
final sharedUsersList = useState<Set<User>>({});
|
final sharedUsersList = useState<Set<User>>({});
|
||||||
|
|
||||||
addNewUsersHandler() {
|
addNewUsersHandler() {
|
||||||
context.popRoute(sharedUsersList.value.map((e) => e.id).toList());
|
context.maybePop(sharedUsersList.value.map((e) => e.id).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTileIcon(User user) {
|
buildTileIcon(User user) {
|
||||||
@@ -127,7 +127,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(Icons.close_rounded),
|
icon: const Icon(Icons.close_rounded),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.popRoute(null);
|
context.maybePop(null);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||||
onPressed: () => context.popRoute(null),
|
onPressed: () => context.maybePop(null),
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: Text("translated_text_options".tr()),
|
title: Text("translated_text_options".tr()),
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
await ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
await ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||||
// ref.watch(assetSelectionProvider.notifier).removeAll();
|
// ref.watch(assetSelectionProvider.notifier).removeAll();
|
||||||
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
|
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
|
||||||
context.popRoute(true);
|
context.maybePop(true);
|
||||||
context
|
context
|
||||||
.navigateTo(const TabControllerRoute(children: [SharingRoute()]));
|
.navigateTo(const TabControllerRoute(children: [SharingRoute()]));
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(Icons.close_rounded),
|
icon: const Icon(Icons.close_rounded),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class AppLogPage extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
},
|
},
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.arrow_back_ios_new_rounded,
|
Icons.arrow_back_ios_new_rounded,
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
|
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_title.provider.dart';
|
import 'package:immich_mobile/providers/album/album_title.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_action_outlined_button.dart';
|
import 'package:immich_mobile/widgets/album/album_action_outlined_button.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_title_text_field.dart';
|
import 'package:immich_mobile/widgets/album/album_title_text_field.dart';
|
||||||
import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
|
import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
@@ -216,7 +216,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
|||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
selectedAssets.value = {};
|
selectedAssets.value = {};
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.close_rounded),
|
icon: const Icon(Icons.close_rounded),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,32 +2,33 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
|
import 'package:immich_mobile/pages/common/video_viewer.page.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/advanced_bottom_sheet.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/advanced_bottom_sheet.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/bottom_gallery_bar.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/bottom_gallery_bar.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/gallery_app_bar.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/gallery_app_bar.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/pages/common/video_viewer.page.dart';
|
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart';
|
import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/src/photo_view_computed_scale.dart';
|
import 'package:immich_mobile/widgets/photo_view/src/photo_view_computed_scale.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/src/photo_view_scale_state.dart';
|
import 'package:immich_mobile/widgets/photo_view/src/photo_view_scale_state.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attributes.dart';
|
import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attributes.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:openapi/api.dart' show ThumbnailFormat;
|
import 'package:openapi/api.dart' show ThumbnailFormat;
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
final settings = ref.watch(appSettingsServiceProvider);
|
final settings = ref.watch(appSettingsServiceProvider);
|
||||||
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
||||||
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
||||||
|
final shouldLoopVideo = useState(AppSettingsEnum.loopVideo.defaultValue);
|
||||||
final isZoomed = useState(false);
|
final isZoomed = useState(false);
|
||||||
final isPlayingVideo = useState(false);
|
final isPlayingVideo = useState(false);
|
||||||
final localPosition = useState<Offset?>(null);
|
final localPosition = useState<Offset?>(null);
|
||||||
@@ -101,6 +103,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
settings.getSetting<bool>(AppSettingsEnum.loadPreview);
|
settings.getSetting<bool>(AppSettingsEnum.loadPreview);
|
||||||
isLoadOriginal.value =
|
isLoadOriginal.value =
|
||||||
settings.getSetting<bool>(AppSettingsEnum.loadOriginal);
|
settings.getSetting<bool>(AppSettingsEnum.loadOriginal);
|
||||||
|
shouldLoopVideo.value =
|
||||||
|
settings.getSetting<bool>(AppSettingsEnum.loopVideo);
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
@@ -174,7 +178,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
final ratio = d.dy / max(d.dx.abs(), 1);
|
final ratio = d.dy / max(d.dx.abs(), 1);
|
||||||
if (d.dy > sensitivity && ratio > ratioThreshold) {
|
if (d.dy > sensitivity && ratio > ratioThreshold) {
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
} else if (d.dy < -sensitivity && ratio < -ratioThreshold) {
|
} else if (d.dy < -sensitivity && ratio < -ratioThreshold) {
|
||||||
showInfo();
|
showInfo();
|
||||||
}
|
}
|
||||||
@@ -261,12 +265,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: false,
|
// Change immersive mode back to normal "edgeToEdge" mode
|
||||||
onPopInvoked: (_) {
|
onPopInvoked: (_) =>
|
||||||
// Change immersive mode back to normal "edgeToEdge" mode
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge),
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
|
||||||
context.pop();
|
|
||||||
},
|
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
@@ -370,6 +371,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
key: ValueKey(a),
|
key: ValueKey(a),
|
||||||
asset: a,
|
asset: a,
|
||||||
isMotionVideo: a.livePhotoVideoId != null,
|
isMotionVideo: a.livePhotoVideoId != null,
|
||||||
|
loopVideo: shouldLoopVideo.value,
|
||||||
placeholder: Image(
|
placeholder: Image(
|
||||||
image: provider,
|
image: provider,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/advanced_settings.dart';
|
import 'package:immich_mobile/widgets/settings/advanced_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_settings.dart';
|
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_settings.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart';
|
import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/image_viewer_quality_setting.dart';
|
|
||||||
import 'package:immich_mobile/widgets/settings/language_settings.dart';
|
import 'package:immich_mobile/widgets/settings/language_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/notification_setting.dart';
|
import 'package:immich_mobile/widgets/settings/notification_setting.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/preference_settings/preference_setting.dart';
|
import 'package:immich_mobile/widgets/settings/preference_settings/preference_setting.dart';
|
||||||
@@ -33,7 +33,7 @@ enum SettingSection {
|
|||||||
SettingSection.preferences => const PreferenceSetting(),
|
SettingSection.preferences => const PreferenceSetting(),
|
||||||
SettingSection.backup => const BackupSettings(),
|
SettingSection.backup => const BackupSettings(),
|
||||||
SettingSection.timeline => const AssetListSettings(),
|
SettingSection.timeline => const AssetListSettings(),
|
||||||
SettingSection.viewer => const ImageViewerQualitySetting(),
|
SettingSection.viewer => const AssetViewerSettings(),
|
||||||
SettingSection.advanced => const AdvancedSettings(),
|
SettingSection.advanced => const AdvancedSettings(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_controller_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_controller_provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/video_player.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/video_player.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
|
import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
// ignore: must_be_immutable
|
|
||||||
class VideoViewerPage extends HookConsumerWidget {
|
class VideoViewerPage extends HookConsumerWidget {
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
final bool isMotionVideo;
|
final bool isMotionVideo;
|
||||||
@@ -20,6 +17,7 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||||||
final Duration hideControlsTimer;
|
final Duration hideControlsTimer;
|
||||||
final bool showControls;
|
final bool showControls;
|
||||||
final bool showDownloadingIndicator;
|
final bool showDownloadingIndicator;
|
||||||
|
final bool loopVideo;
|
||||||
|
|
||||||
const VideoViewerPage({
|
const VideoViewerPage({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -29,6 +27,7 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||||||
this.showControls = true,
|
this.showControls = true,
|
||||||
this.hideControlsTimer = const Duration(seconds: 5),
|
this.hideControlsTimer = const Duration(seconds: 5),
|
||||||
this.showDownloadingIndicator = true,
|
this.showDownloadingIndicator = true,
|
||||||
|
this.loopVideo = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -76,7 +75,9 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||||||
// Also sets the error if there is an error in the playback
|
// Also sets the error if there is an error in the playback
|
||||||
void updateVideoPlayback() {
|
void updateVideoPlayback() {
|
||||||
final videoPlayback = VideoPlaybackValue.fromController(controller);
|
final videoPlayback = VideoPlaybackValue.fromController(controller);
|
||||||
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
|
if (!loopVideo) {
|
||||||
|
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
|
||||||
|
}
|
||||||
final state = videoPlayback.state;
|
final state = videoPlayback.state;
|
||||||
|
|
||||||
// Enable the WakeLock while the video is playing
|
// Enable the WakeLock while the video is playing
|
||||||
@@ -156,6 +157,7 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||||||
hideControlsTimer: hideControlsTimer,
|
hideControlsTimer: hideControlsTimer,
|
||||||
showControls: showControls,
|
showControls: showControls,
|
||||||
showDownloadingIndicator: showDownloadingIndicator,
|
showDownloadingIndicator: showDownloadingIndicator,
|
||||||
|
loopVideo: loopVideo,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class ArchivePage extends HookConsumerWidget {
|
|||||||
final count = archivedAssets.value?.totalAssets.toString() ?? "?";
|
final count = archivedAssets.value?.totalAssets.toString() ?? "?";
|
||||||
return AppBar(
|
return AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class FavoritesPage extends HookConsumerWidget {
|
|||||||
AppBar buildAppBar() {
|
AppBar buildAppBar() {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ class TrashPage extends HookConsumerWidget {
|
|||||||
return AppBar(
|
return AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: !selectionEnabledHook.value
|
onPressed: !selectionEnabledHook.value
|
||||||
? () => context.popRoute()
|
? () => context.maybePop()
|
||||||
: () {
|
: () {
|
||||||
selectionEnabledHook.value = false;
|
selectionEnabledHook.value = false;
|
||||||
selection.value = {};
|
selection.value = {};
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
child: const Text('permission_onboarding_back').tr(),
|
child: const Text('permission_onboarding_back').tr(),
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/models/memories/memory.model.dart';
|
import 'package:immich_mobile/models/memories/memory.model.dart';
|
||||||
|
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||||
|
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
||||||
import 'package:immich_mobile/widgets/memories/memory_bottom_info.dart';
|
import 'package:immich_mobile/widgets/memories/memory_bottom_info.dart';
|
||||||
import 'package:immich_mobile/widgets/memories/memory_card.dart';
|
import 'package:immich_mobile/widgets/memories/memory_card.dart';
|
||||||
import 'package:immich_mobile/widgets/memories/memory_epilogue.dart';
|
import 'package:immich_mobile/widgets/memories/memory_epilogue.dart';
|
||||||
import 'package:immich_mobile/widgets/memories/memory_progress_indicator.dart';
|
import 'package:immich_mobile/widgets/memories/memory_progress_indicator.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
||||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class MemoryPage extends HookConsumerWidget {
|
class MemoryPage extends HookConsumerWidget {
|
||||||
@@ -153,7 +153,7 @@ class MemoryPage extends HookConsumerWidget {
|
|||||||
final offset = notification.metrics.pixels;
|
final offset = notification.metrics.pixels;
|
||||||
if (isEpiloguePage &&
|
if (isEpiloguePage &&
|
||||||
(offset > notification.metrics.maxScrollExtent + 150)) {
|
(offset > notification.metrics.maxScrollExtent + 150)) {
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ class MemoryPage extends HookConsumerWidget {
|
|||||||
// auto_route doesn't invoke pop scope, so
|
// auto_route doesn't invoke pop scope, so
|
||||||
// turn off full screen mode here
|
// turn off full screen mode here
|
||||||
// https://github.com/Milad-Akarie/auto_route_library/issues/1799
|
// https://github.com/Milad-Akarie/auto_route_library/issues/1799
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
SystemChrome.setEnabledSystemUIMode(
|
SystemChrome.setEnabledSystemUIMode(
|
||||||
SystemUiMode.edgeToEdge,
|
SystemUiMode.edgeToEdge,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class AllMotionPhotosPage extends HookConsumerWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('motion_photos_page_title').tr(),
|
title: const Text('motion_photos_page_title').tr(),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class AllPeoplePage extends HookConsumerWidget {
|
|||||||
'all_people_page_title',
|
'all_people_page_title',
|
||||||
).tr(),
|
).tr(),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class AllPlacesPage extends HookConsumerWidget {
|
|||||||
'curated_location_page_title',
|
'curated_location_page_title',
|
||||||
).tr(),
|
).tr(),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class AllVideosPage extends HookConsumerWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('all_videos_page_title').tr(),
|
title: const Text('all_videos_page_title').tr(),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onClose([LatLng? selected]) {
|
void onClose([LatLng? selected]) {
|
||||||
context.popRoute(selected);
|
context.maybePop(selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getCurrentLocation() async {
|
Future<void> getCurrentLocation() async {
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class PersonResultPage extends HookConsumerWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(name.value),
|
title: Text(name.value),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class RecentlyAddedPage extends HookConsumerWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('recently_added_page_title').tr(),
|
title: const Text('recently_added_page_title').tr(),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||||
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
|
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
|
||||||
import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart';
|
import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart';
|
||||||
import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart';
|
import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart';
|
||||||
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
|
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
|
||||||
@@ -15,8 +17,6 @@ import 'package:immich_mobile/widgets/search/search_filter/media_type_picker.dar
|
|||||||
import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart';
|
import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart';
|
||||||
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
|
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
|
||||||
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
|
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
||||||
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
|
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@@ -480,9 +480,7 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||||
onPressed: () {
|
onPressed: () => context.router.maybePop(),
|
||||||
context.router.pop();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
title: TextField(
|
title: TextField(
|
||||||
controller: textSearchController,
|
controller: textSearchController,
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
alignment: Alignment.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
"share_done",
|
"share_done",
|
||||||
@@ -431,7 +431,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
changeExpiry: changeExpiry,
|
changeExpiry: changeExpiry,
|
||||||
);
|
);
|
||||||
ref.invalidate(sharedLinksStateProvider);
|
ref.invalidate(sharedLinksStateProvider);
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -1,35 +1,32 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/models/memories/memory.model.dart';
|
|
||||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
|
||||||
import 'package:immich_mobile/pages/common/activities.page.dart';
|
|
||||||
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
|
|
||||||
import 'package:immich_mobile/pages/common/album_options.page.dart';
|
|
||||||
import 'package:immich_mobile/pages/common/album_viewer.page.dart';
|
|
||||||
import 'package:immich_mobile/pages/common/album_asset_selection.page.dart';
|
|
||||||
import 'package:immich_mobile/pages/common/create_album.page.dart';
|
|
||||||
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
|
||||||
import 'package:immich_mobile/pages/common/album_additional_shared_user_selection.page.dart';
|
|
||||||
import 'package:immich_mobile/pages/common/album_shared_user_selection.page.dart';
|
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
|
||||||
import 'package:immich_mobile/routing/auth_guard.dart';
|
|
||||||
import 'package:immich_mobile/routing/custom_transition_builders.dart';
|
|
||||||
import 'package:immich_mobile/routing/duplicate_guard.dart';
|
|
||||||
import 'package:immich_mobile/routing/backup_permission_guard.dart';
|
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/logger_message.entity.dart';
|
import 'package:immich_mobile/entities/logger_message.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
|
||||||
import 'package:immich_mobile/pages/common/app_log_detail.page.dart';
|
import 'package:immich_mobile/models/memories/memory.model.dart';
|
||||||
import 'package:immich_mobile/pages/common/app_log.page.dart';
|
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||||
|
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
||||||
import 'package:immich_mobile/pages/backup/album_preview.page.dart';
|
import 'package:immich_mobile/pages/backup/album_preview.page.dart';
|
||||||
import 'package:immich_mobile/pages/backup/backup_album_selection.page.dart';
|
import 'package:immich_mobile/pages/backup/backup_album_selection.page.dart';
|
||||||
import 'package:immich_mobile/pages/backup/backup_controller.page.dart';
|
import 'package:immich_mobile/pages/backup/backup_controller.page.dart';
|
||||||
import 'package:immich_mobile/pages/backup/backup_options.page.dart';
|
import 'package:immich_mobile/pages/backup/backup_options.page.dart';
|
||||||
import 'package:immich_mobile/pages/backup/failed_backup_status.page.dart';
|
import 'package:immich_mobile/pages/backup/failed_backup_status.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/activities.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/album_additional_shared_user_selection.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/album_asset_selection.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/album_options.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/album_shared_user_selection.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/album_viewer.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/app_log.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/app_log_detail.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/create_album.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/gallery_viewer.page.dart';
|
import 'package:immich_mobile/pages/common/gallery_viewer.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/settings.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/splash_screen.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/tab_controller.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/archive.page.dart';
|
import 'package:immich_mobile/pages/library/archive.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/favorite.page.dart';
|
import 'package:immich_mobile/pages/library/favorite.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/library.page.dart';
|
import 'package:immich_mobile/pages/library/library.page.dart';
|
||||||
@@ -43,21 +40,23 @@ import 'package:immich_mobile/pages/search/all_motion_videos.page.dart';
|
|||||||
import 'package:immich_mobile/pages/search/all_people.page.dart';
|
import 'package:immich_mobile/pages/search/all_people.page.dart';
|
||||||
import 'package:immich_mobile/pages/search/all_places.page.dart';
|
import 'package:immich_mobile/pages/search/all_places.page.dart';
|
||||||
import 'package:immich_mobile/pages/search/all_videos.page.dart';
|
import 'package:immich_mobile/pages/search/all_videos.page.dart';
|
||||||
import 'package:immich_mobile/pages/search/map/map_location_picker.page.dart';
|
|
||||||
import 'package:immich_mobile/pages/search/map/map.page.dart';
|
import 'package:immich_mobile/pages/search/map/map.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/search/map/map_location_picker.page.dart';
|
||||||
import 'package:immich_mobile/pages/search/person_result.page.dart';
|
import 'package:immich_mobile/pages/search/person_result.page.dart';
|
||||||
import 'package:immich_mobile/pages/search/recently_added.page.dart';
|
import 'package:immich_mobile/pages/search/recently_added.page.dart';
|
||||||
import 'package:immich_mobile/pages/search/search_input.page.dart';
|
|
||||||
import 'package:immich_mobile/pages/search/search.page.dart';
|
import 'package:immich_mobile/pages/search/search.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/settings.page.dart';
|
import 'package:immich_mobile/pages/search/search_input.page.dart';
|
||||||
import 'package:immich_mobile/pages/sharing/partner/partner_detail.page.dart';
|
|
||||||
import 'package:immich_mobile/pages/sharing/partner/partner.page.dart';
|
import 'package:immich_mobile/pages/sharing/partner/partner.page.dart';
|
||||||
import 'package:immich_mobile/pages/sharing/shared_link/shared_link_edit.page.dart';
|
import 'package:immich_mobile/pages/sharing/partner/partner_detail.page.dart';
|
||||||
import 'package:immich_mobile/pages/sharing/shared_link/shared_link.page.dart';
|
import 'package:immich_mobile/pages/sharing/shared_link/shared_link.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/sharing/shared_link/shared_link_edit.page.dart';
|
||||||
import 'package:immich_mobile/pages/sharing/sharing.page.dart';
|
import 'package:immich_mobile/pages/sharing/sharing.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/splash_screen.page.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/pages/common/tab_controller.page.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/pages/common/video_viewer.page.dart';
|
import 'package:immich_mobile/routing/auth_guard.dart';
|
||||||
|
import 'package:immich_mobile/routing/backup_permission_guard.dart';
|
||||||
|
import 'package:immich_mobile/routing/custom_transition_builders.dart';
|
||||||
|
import 'package:immich_mobile/routing/duplicate_guard.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
@@ -120,10 +119,6 @@ class AppRouter extends _$AppRouter {
|
|||||||
guards: [_authGuard, _duplicateGuard],
|
guards: [_authGuard, _duplicateGuard],
|
||||||
transitionsBuilder: CustomTransitionsBuilders.zoomedPage,
|
transitionsBuilder: CustomTransitionsBuilders.zoomedPage,
|
||||||
),
|
),
|
||||||
AutoRoute(
|
|
||||||
page: VideoViewerRoute.page,
|
|
||||||
guards: [_authGuard, _duplicateGuard],
|
|
||||||
),
|
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
page: BackupControllerRoute.page,
|
page: BackupControllerRoute.page,
|
||||||
guards: [_authGuard, _duplicateGuard, _backupPermissionGuard],
|
guards: [_authGuard, _duplicateGuard, _backupPermissionGuard],
|
||||||
|
|||||||
+180
-256
@@ -21,6 +21,29 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||||||
child: const ActivitiesPage(),
|
child: const ActivitiesPage(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
AlbumAdditionalSharedUserSelectionRoute.name: (routeData) {
|
||||||
|
final args =
|
||||||
|
routeData.argsAs<AlbumAdditionalSharedUserSelectionRouteArgs>();
|
||||||
|
return AutoRoutePage<List<String>?>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: AlbumAdditionalSharedUserSelectionPage(
|
||||||
|
key: args.key,
|
||||||
|
album: args.album,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
AlbumAssetSelectionRoute.name: (routeData) {
|
||||||
|
final args = routeData.argsAs<AlbumAssetSelectionRouteArgs>();
|
||||||
|
return AutoRoutePage<AssetSelectionPageResult?>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: AlbumAssetSelectionPage(
|
||||||
|
key: args.key,
|
||||||
|
existingAssets: args.existingAssets,
|
||||||
|
canDeselect: args.canDeselect,
|
||||||
|
query: args.query,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
AlbumOptionsRoute.name: (routeData) {
|
AlbumOptionsRoute.name: (routeData) {
|
||||||
final args = routeData.argsAs<AlbumOptionsRouteArgs>();
|
final args = routeData.argsAs<AlbumOptionsRouteArgs>();
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
@@ -41,6 +64,16 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
AlbumSharedUserSelectionRoute.name: (routeData) {
|
||||||
|
final args = routeData.argsAs<AlbumSharedUserSelectionRouteArgs>();
|
||||||
|
return AutoRoutePage<List<String>>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: AlbumSharedUserSelectionPage(
|
||||||
|
key: args.key,
|
||||||
|
assets: args.assets,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
AlbumViewerRoute.name: (routeData) {
|
AlbumViewerRoute.name: (routeData) {
|
||||||
final args = routeData.argsAs<AlbumViewerRouteArgs>();
|
final args = routeData.argsAs<AlbumViewerRouteArgs>();
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
@@ -97,18 +130,6 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||||||
child: const ArchivePage(),
|
child: const ArchivePage(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
AlbumAssetSelectionRoute.name: (routeData) {
|
|
||||||
final args = routeData.argsAs<AssetSelectionRouteArgs>();
|
|
||||||
return AutoRoutePage<AssetSelectionPageResult?>(
|
|
||||||
routeData: routeData,
|
|
||||||
child: AlbumAssetSelectionPage(
|
|
||||||
key: args.key,
|
|
||||||
existingAssets: args.existingAssets,
|
|
||||||
canDeselect: args.canDeselect,
|
|
||||||
query: args.query,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
BackupAlbumSelectionRoute.name: (routeData) {
|
BackupAlbumSelectionRoute.name: (routeData) {
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
@@ -170,12 +191,6 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
PhotosRoute.name: (routeData) {
|
|
||||||
return AutoRoutePage<dynamic>(
|
|
||||||
routeData: routeData,
|
|
||||||
child: const PhotosPage(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
LibraryRoute.name: (routeData) {
|
LibraryRoute.name: (routeData) {
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
@@ -249,6 +264,12 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
PhotosRoute.name: (routeData) {
|
||||||
|
return AutoRoutePage<dynamic>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: const PhotosPage(),
|
||||||
|
);
|
||||||
|
},
|
||||||
RecentlyAddedRoute.name: (routeData) {
|
RecentlyAddedRoute.name: (routeData) {
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
@@ -272,26 +293,6 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||||||
child: const SearchPage(),
|
child: const SearchPage(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
AlbumAdditionalSharedUserSelectionRoute.name: (routeData) {
|
|
||||||
final args = routeData.argsAs<SelectAdditionalUserForSharingRouteArgs>();
|
|
||||||
return AutoRoutePage<List<String>?>(
|
|
||||||
routeData: routeData,
|
|
||||||
child: AlbumAdditionalSharedUserSelectionPage(
|
|
||||||
key: args.key,
|
|
||||||
album: args.album,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
AlbumSharedUserSelectionRoute.name: (routeData) {
|
|
||||||
final args = routeData.argsAs<SelectUserForSharingRouteArgs>();
|
|
||||||
return AutoRoutePage<List<String>>(
|
|
||||||
routeData: routeData,
|
|
||||||
child: AlbumSharedUserSelectionPage(
|
|
||||||
key: args.key,
|
|
||||||
assets: args.assets,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
SettingsRoute.name: (routeData) {
|
SettingsRoute.name: (routeData) {
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
@@ -351,21 +352,6 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||||||
child: const TrashPage(),
|
child: const TrashPage(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
VideoViewerRoute.name: (routeData) {
|
|
||||||
final args = routeData.argsAs<VideoViewerRouteArgs>();
|
|
||||||
return AutoRoutePage<dynamic>(
|
|
||||||
routeData: routeData,
|
|
||||||
child: VideoViewerPage(
|
|
||||||
key: args.key,
|
|
||||||
asset: args.asset,
|
|
||||||
isMotionVideo: args.isMotionVideo,
|
|
||||||
placeholder: args.placeholder,
|
|
||||||
showControls: args.showControls,
|
|
||||||
hideControlsTimer: args.hideControlsTimer,
|
|
||||||
showDownloadingIndicator: args.showDownloadingIndicator,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +369,94 @@ class ActivitiesRoute extends PageRouteInfo<void> {
|
|||||||
static const PageInfo<void> page = PageInfo<void>(name);
|
static const PageInfo<void> page = PageInfo<void>(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [AlbumAdditionalSharedUserSelectionPage]
|
||||||
|
class AlbumAdditionalSharedUserSelectionRoute
|
||||||
|
extends PageRouteInfo<AlbumAdditionalSharedUserSelectionRouteArgs> {
|
||||||
|
AlbumAdditionalSharedUserSelectionRoute({
|
||||||
|
Key? key,
|
||||||
|
required Album album,
|
||||||
|
List<PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
AlbumAdditionalSharedUserSelectionRoute.name,
|
||||||
|
args: AlbumAdditionalSharedUserSelectionRouteArgs(
|
||||||
|
key: key,
|
||||||
|
album: album,
|
||||||
|
),
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'AlbumAdditionalSharedUserSelectionRoute';
|
||||||
|
|
||||||
|
static const PageInfo<AlbumAdditionalSharedUserSelectionRouteArgs> page =
|
||||||
|
PageInfo<AlbumAdditionalSharedUserSelectionRouteArgs>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlbumAdditionalSharedUserSelectionRouteArgs {
|
||||||
|
const AlbumAdditionalSharedUserSelectionRouteArgs({
|
||||||
|
this.key,
|
||||||
|
required this.album,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
|
|
||||||
|
final Album album;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'AlbumAdditionalSharedUserSelectionRouteArgs{key: $key, album: $album}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [AlbumAssetSelectionPage]
|
||||||
|
class AlbumAssetSelectionRoute
|
||||||
|
extends PageRouteInfo<AlbumAssetSelectionRouteArgs> {
|
||||||
|
AlbumAssetSelectionRoute({
|
||||||
|
Key? key,
|
||||||
|
required Set<Asset> existingAssets,
|
||||||
|
bool canDeselect = false,
|
||||||
|
required QueryBuilder<Asset, Asset, QAfterSortBy>? query,
|
||||||
|
List<PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
AlbumAssetSelectionRoute.name,
|
||||||
|
args: AlbumAssetSelectionRouteArgs(
|
||||||
|
key: key,
|
||||||
|
existingAssets: existingAssets,
|
||||||
|
canDeselect: canDeselect,
|
||||||
|
query: query,
|
||||||
|
),
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'AlbumAssetSelectionRoute';
|
||||||
|
|
||||||
|
static const PageInfo<AlbumAssetSelectionRouteArgs> page =
|
||||||
|
PageInfo<AlbumAssetSelectionRouteArgs>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlbumAssetSelectionRouteArgs {
|
||||||
|
const AlbumAssetSelectionRouteArgs({
|
||||||
|
this.key,
|
||||||
|
required this.existingAssets,
|
||||||
|
this.canDeselect = false,
|
||||||
|
required this.query,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
|
|
||||||
|
final Set<Asset> existingAssets;
|
||||||
|
|
||||||
|
final bool canDeselect;
|
||||||
|
|
||||||
|
final QueryBuilder<Asset, Asset, QAfterSortBy>? query;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'AlbumAssetSelectionRouteArgs{key: $key, existingAssets: $existingAssets, canDeselect: $canDeselect, query: $query}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [AlbumOptionsPage]
|
/// [AlbumOptionsPage]
|
||||||
class AlbumOptionsRoute extends PageRouteInfo<AlbumOptionsRouteArgs> {
|
class AlbumOptionsRoute extends PageRouteInfo<AlbumOptionsRouteArgs> {
|
||||||
@@ -459,6 +533,45 @@ class AlbumPreviewRouteArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [AlbumSharedUserSelectionPage]
|
||||||
|
class AlbumSharedUserSelectionRoute
|
||||||
|
extends PageRouteInfo<AlbumSharedUserSelectionRouteArgs> {
|
||||||
|
AlbumSharedUserSelectionRoute({
|
||||||
|
Key? key,
|
||||||
|
required Set<Asset> assets,
|
||||||
|
List<PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
AlbumSharedUserSelectionRoute.name,
|
||||||
|
args: AlbumSharedUserSelectionRouteArgs(
|
||||||
|
key: key,
|
||||||
|
assets: assets,
|
||||||
|
),
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'AlbumSharedUserSelectionRoute';
|
||||||
|
|
||||||
|
static const PageInfo<AlbumSharedUserSelectionRouteArgs> page =
|
||||||
|
PageInfo<AlbumSharedUserSelectionRouteArgs>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlbumSharedUserSelectionRouteArgs {
|
||||||
|
const AlbumSharedUserSelectionRouteArgs({
|
||||||
|
this.key,
|
||||||
|
required this.assets,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
|
|
||||||
|
final Set<Asset> assets;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'AlbumSharedUserSelectionRouteArgs{key: $key, assets: $assets}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [AlbumViewerPage]
|
/// [AlbumViewerPage]
|
||||||
class AlbumViewerRoute extends PageRouteInfo<AlbumViewerRouteArgs> {
|
class AlbumViewerRoute extends PageRouteInfo<AlbumViewerRouteArgs> {
|
||||||
@@ -619,54 +732,6 @@ class ArchiveRoute extends PageRouteInfo<void> {
|
|||||||
static const PageInfo<void> page = PageInfo<void>(name);
|
static const PageInfo<void> page = PageInfo<void>(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [AlbumAssetSelectionPage]
|
|
||||||
class AlbumAssetSelectionRoute extends PageRouteInfo<AssetSelectionRouteArgs> {
|
|
||||||
AlbumAssetSelectionRoute({
|
|
||||||
Key? key,
|
|
||||||
required Set<Asset> existingAssets,
|
|
||||||
bool canDeselect = false,
|
|
||||||
required QueryBuilder<Asset, Asset, QAfterSortBy>? query,
|
|
||||||
List<PageRouteInfo>? children,
|
|
||||||
}) : super(
|
|
||||||
AlbumAssetSelectionRoute.name,
|
|
||||||
args: AssetSelectionRouteArgs(
|
|
||||||
key: key,
|
|
||||||
existingAssets: existingAssets,
|
|
||||||
canDeselect: canDeselect,
|
|
||||||
query: query,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AlbumAssetSelectionRoute';
|
|
||||||
|
|
||||||
static const PageInfo<AssetSelectionRouteArgs> page =
|
|
||||||
PageInfo<AssetSelectionRouteArgs>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
class AssetSelectionRouteArgs {
|
|
||||||
const AssetSelectionRouteArgs({
|
|
||||||
this.key,
|
|
||||||
required this.existingAssets,
|
|
||||||
this.canDeselect = false,
|
|
||||||
required this.query,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
|
||||||
|
|
||||||
final Set<Asset> existingAssets;
|
|
||||||
|
|
||||||
final bool canDeselect;
|
|
||||||
|
|
||||||
final QueryBuilder<Asset, Asset, QAfterSortBy>? query;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'AssetSelectionRouteArgs{key: $key, existingAssets: $existingAssets, canDeselect: $canDeselect, query: $query}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [BackupAlbumSelectionPage]
|
/// [BackupAlbumSelectionPage]
|
||||||
class BackupAlbumSelectionRoute extends PageRouteInfo<void> {
|
class BackupAlbumSelectionRoute extends PageRouteInfo<void> {
|
||||||
@@ -852,20 +917,6 @@ class GalleryViewerRouteArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [PhotosPage]
|
|
||||||
class PhotosRoute extends PageRouteInfo<void> {
|
|
||||||
const PhotosRoute({List<PageRouteInfo>? children})
|
|
||||||
: super(
|
|
||||||
PhotosRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'PhotosRoute';
|
|
||||||
|
|
||||||
static const PageInfo<void> page = PageInfo<void>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [LibraryPage]
|
/// [LibraryPage]
|
||||||
class LibraryRoute extends PageRouteInfo<void> {
|
class LibraryRoute extends PageRouteInfo<void> {
|
||||||
@@ -1097,6 +1148,20 @@ class PersonResultRouteArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [PhotosPage]
|
||||||
|
class PhotosRoute extends PageRouteInfo<void> {
|
||||||
|
const PhotosRoute({List<PageRouteInfo>? children})
|
||||||
|
: super(
|
||||||
|
PhotosRoute.name,
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'PhotosRoute';
|
||||||
|
|
||||||
|
static const PageInfo<void> page = PageInfo<void>(name);
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [RecentlyAddedPage]
|
/// [RecentlyAddedPage]
|
||||||
class RecentlyAddedRoute extends PageRouteInfo<void> {
|
class RecentlyAddedRoute extends PageRouteInfo<void> {
|
||||||
@@ -1163,84 +1228,6 @@ class SearchRoute extends PageRouteInfo<void> {
|
|||||||
static const PageInfo<void> page = PageInfo<void>(name);
|
static const PageInfo<void> page = PageInfo<void>(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [AlbumAdditionalSharedUserSelectionPage]
|
|
||||||
class AlbumAdditionalSharedUserSelectionRoute
|
|
||||||
extends PageRouteInfo<SelectAdditionalUserForSharingRouteArgs> {
|
|
||||||
AlbumAdditionalSharedUserSelectionRoute({
|
|
||||||
Key? key,
|
|
||||||
required Album album,
|
|
||||||
List<PageRouteInfo>? children,
|
|
||||||
}) : super(
|
|
||||||
AlbumAdditionalSharedUserSelectionRoute.name,
|
|
||||||
args: SelectAdditionalUserForSharingRouteArgs(
|
|
||||||
key: key,
|
|
||||||
album: album,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AlbumAdditionalSharedUserSelectionRoute';
|
|
||||||
|
|
||||||
static const PageInfo<SelectAdditionalUserForSharingRouteArgs> page =
|
|
||||||
PageInfo<SelectAdditionalUserForSharingRouteArgs>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SelectAdditionalUserForSharingRouteArgs {
|
|
||||||
const SelectAdditionalUserForSharingRouteArgs({
|
|
||||||
this.key,
|
|
||||||
required this.album,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
|
||||||
|
|
||||||
final Album album;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SelectAdditionalUserForSharingRouteArgs{key: $key, album: $album}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [AlbumSharedUserSelectionPage]
|
|
||||||
class AlbumSharedUserSelectionRoute
|
|
||||||
extends PageRouteInfo<SelectUserForSharingRouteArgs> {
|
|
||||||
AlbumSharedUserSelectionRoute({
|
|
||||||
Key? key,
|
|
||||||
required Set<Asset> assets,
|
|
||||||
List<PageRouteInfo>? children,
|
|
||||||
}) : super(
|
|
||||||
AlbumSharedUserSelectionRoute.name,
|
|
||||||
args: SelectUserForSharingRouteArgs(
|
|
||||||
key: key,
|
|
||||||
assets: assets,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AlbumSharedUserSelectionRoute';
|
|
||||||
|
|
||||||
static const PageInfo<SelectUserForSharingRouteArgs> page =
|
|
||||||
PageInfo<SelectUserForSharingRouteArgs>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SelectUserForSharingRouteArgs {
|
|
||||||
const SelectUserForSharingRouteArgs({
|
|
||||||
this.key,
|
|
||||||
required this.assets,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
|
||||||
|
|
||||||
final Set<Asset> assets;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SelectUserForSharingRouteArgs{key: $key, assets: $assets}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [SettingsPage]
|
/// [SettingsPage]
|
||||||
class SettingsRoute extends PageRouteInfo<void> {
|
class SettingsRoute extends PageRouteInfo<void> {
|
||||||
@@ -1410,66 +1397,3 @@ class TrashRoute extends PageRouteInfo<void> {
|
|||||||
|
|
||||||
static const PageInfo<void> page = PageInfo<void>(name);
|
static const PageInfo<void> page = PageInfo<void>(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [VideoViewerPage]
|
|
||||||
class VideoViewerRoute extends PageRouteInfo<VideoViewerRouteArgs> {
|
|
||||||
VideoViewerRoute({
|
|
||||||
Key? key,
|
|
||||||
required Asset asset,
|
|
||||||
bool isMotionVideo = false,
|
|
||||||
Widget? placeholder,
|
|
||||||
bool showControls = true,
|
|
||||||
Duration hideControlsTimer = const Duration(seconds: 5),
|
|
||||||
bool showDownloadingIndicator = true,
|
|
||||||
List<PageRouteInfo>? children,
|
|
||||||
}) : super(
|
|
||||||
VideoViewerRoute.name,
|
|
||||||
args: VideoViewerRouteArgs(
|
|
||||||
key: key,
|
|
||||||
asset: asset,
|
|
||||||
isMotionVideo: isMotionVideo,
|
|
||||||
placeholder: placeholder,
|
|
||||||
showControls: showControls,
|
|
||||||
hideControlsTimer: hideControlsTimer,
|
|
||||||
showDownloadingIndicator: showDownloadingIndicator,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'VideoViewerRoute';
|
|
||||||
|
|
||||||
static const PageInfo<VideoViewerRouteArgs> page =
|
|
||||||
PageInfo<VideoViewerRouteArgs>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
class VideoViewerRouteArgs {
|
|
||||||
const VideoViewerRouteArgs({
|
|
||||||
this.key,
|
|
||||||
required this.asset,
|
|
||||||
this.isMotionVideo = false,
|
|
||||||
this.placeholder,
|
|
||||||
this.showControls = true,
|
|
||||||
this.hideControlsTimer = const Duration(seconds: 5),
|
|
||||||
this.showDownloadingIndicator = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
|
||||||
|
|
||||||
final Asset asset;
|
|
||||||
|
|
||||||
final bool isMotionVideo;
|
|
||||||
|
|
||||||
final Widget? placeholder;
|
|
||||||
|
|
||||||
final bool showControls;
|
|
||||||
|
|
||||||
final Duration hideControlsTimer;
|
|
||||||
|
|
||||||
final bool showDownloadingIndicator;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'VideoViewerRouteArgs{key: $key, asset: $asset, isMotionVideo: $isMotionVideo, placeholder: $placeholder, showControls: $showControls, hideControlsTimer: $hideControlsTimer, showDownloadingIndicator: $showDownloadingIndicator}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ enum AppSettingsEnum<T> {
|
|||||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
|
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
|
||||||
logLevel<int>(StoreKey.logLevel, null, 5), // Level.INFO = 5
|
logLevel<int>(StoreKey.logLevel, null, 5), // Level.INFO = 5
|
||||||
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
|
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
|
||||||
|
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),
|
||||||
mapThemeMode<int>(StoreKey.mapThemeMode, null, 0),
|
mapThemeMode<int>(StoreKey.mapThemeMode, null, 0),
|
||||||
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
|
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
|
||||||
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
|
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ ChewieController useChewieController({
|
|||||||
bool allowFullScreen = false,
|
bool allowFullScreen = false,
|
||||||
bool allowedScreenSleep = false,
|
bool allowedScreenSleep = false,
|
||||||
bool showControls = true,
|
bool showControls = true,
|
||||||
|
bool loopVideo = false,
|
||||||
Widget? customControls,
|
Widget? customControls,
|
||||||
Widget? placeholder,
|
Widget? placeholder,
|
||||||
Duration hideControlsTimer = const Duration(seconds: 1),
|
Duration hideControlsTimer = const Duration(seconds: 1),
|
||||||
@@ -36,6 +37,7 @@ ChewieController useChewieController({
|
|||||||
hideControlsTimer: hideControlsTimer,
|
hideControlsTimer: hideControlsTimer,
|
||||||
showControlsOnInitialize: showControlsOnInitialize,
|
showControlsOnInitialize: showControlsOnInitialize,
|
||||||
showControls: showControls,
|
showControls: showControls,
|
||||||
|
loopVideo: loopVideo,
|
||||||
allowedScreenSleep: allowedScreenSleep,
|
allowedScreenSleep: allowedScreenSleep,
|
||||||
onPlaying: onPlaying,
|
onPlaying: onPlaying,
|
||||||
onPaused: onPaused,
|
onPaused: onPaused,
|
||||||
@@ -53,6 +55,7 @@ class _ChewieControllerHook extends Hook<ChewieController> {
|
|||||||
final bool allowFullScreen;
|
final bool allowFullScreen;
|
||||||
final bool allowedScreenSleep;
|
final bool allowedScreenSleep;
|
||||||
final bool showControls;
|
final bool showControls;
|
||||||
|
final bool loopVideo;
|
||||||
final Widget? customControls;
|
final Widget? customControls;
|
||||||
final Widget? placeholder;
|
final Widget? placeholder;
|
||||||
final Duration hideControlsTimer;
|
final Duration hideControlsTimer;
|
||||||
@@ -71,6 +74,7 @@ class _ChewieControllerHook extends Hook<ChewieController> {
|
|||||||
this.allowFullScreen = false,
|
this.allowFullScreen = false,
|
||||||
this.allowedScreenSleep = false,
|
this.allowedScreenSleep = false,
|
||||||
this.showControls = true,
|
this.showControls = true,
|
||||||
|
this.loopVideo = false,
|
||||||
this.customControls,
|
this.customControls,
|
||||||
this.placeholder,
|
this.placeholder,
|
||||||
this.hideControlsTimer = const Duration(seconds: 3),
|
this.hideControlsTimer = const Duration(seconds: 3),
|
||||||
@@ -94,6 +98,7 @@ class _ChewieControllerHookState
|
|||||||
allowFullScreen: hook.allowFullScreen,
|
allowFullScreen: hook.allowFullScreen,
|
||||||
allowedScreenSleep: hook.allowedScreenSleep,
|
allowedScreenSleep: hook.allowedScreenSleep,
|
||||||
showControls: hook.showControls,
|
showControls: hook.showControls,
|
||||||
|
looping: hook.loopVideo,
|
||||||
customControls: hook.customControls,
|
customControls: hook.customControls,
|
||||||
placeholder: hook.placeholder,
|
placeholder: hook.placeholder,
|
||||||
hideControlsTimer: hook.hideControlsTimer,
|
hideControlsTimer: hook.hideControlsTimer,
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: () async => await context.popRoute(),
|
onPressed: () async => await context.maybePop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
splashRadius: 25,
|
splashRadius: 25,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
if (isDeleted && isParent) {
|
if (isDeleted && isParent) {
|
||||||
if (totalAssets == 1) {
|
if (totalAssets == 1) {
|
||||||
// Handle only one asset
|
// Handle only one asset
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
} else {
|
} else {
|
||||||
// Go to next page otherwise
|
// Go to next page otherwise
|
||||||
controller.nextPage(
|
controller.nextPage(
|
||||||
@@ -181,7 +181,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
stackElements.elementAt(stackIndex),
|
stackElements.elementAt(stackIndex),
|
||||||
);
|
);
|
||||||
ctx.pop();
|
ctx.pop();
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
},
|
},
|
||||||
title: const Text(
|
title: const Text(
|
||||||
"viewer_stack_use_as_main_asset",
|
"viewer_stack_use_as_main_asset",
|
||||||
@@ -208,7 +208,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
childrenToRemove: [asset],
|
childrenToRemove: [asset],
|
||||||
);
|
);
|
||||||
ctx.pop();
|
ctx.pop();
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
} else {
|
} else {
|
||||||
await ref.read(assetStackServiceProvider).updateStack(
|
await ref.read(assetStackServiceProvider).updateStack(
|
||||||
asset,
|
asset,
|
||||||
@@ -236,7 +236,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
childrenToRemove: stack,
|
childrenToRemove: stack,
|
||||||
);
|
);
|
||||||
ctx.pop();
|
ctx.pop();
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
},
|
},
|
||||||
title: const Text(
|
title: const Text(
|
||||||
"viewer_unstack",
|
"viewer_unstack",
|
||||||
@@ -267,7 +267,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
handleArchive() {
|
handleArchive() {
|
||||||
ref.read(assetProvider.notifier).toggleArchive([asset]);
|
ref.read(assetProvider.notifier).toggleArchive([asset]);
|
||||||
if (isParent) {
|
if (isParent) {
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
removeAssetFromStack();
|
removeAssetFromStack();
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ class TopControlAppBar extends HookConsumerWidget {
|
|||||||
Widget buildBackButton() {
|
Widget buildBackButton() {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back_ios_new_rounded,
|
Icons.arrow_back_ios_new_rounded,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class VideoPlayerViewer extends HookConsumerWidget {
|
|||||||
final Duration hideControlsTimer;
|
final Duration hideControlsTimer;
|
||||||
final bool showControls;
|
final bool showControls;
|
||||||
final bool showDownloadingIndicator;
|
final bool showDownloadingIndicator;
|
||||||
|
final bool loopVideo;
|
||||||
|
|
||||||
const VideoPlayerViewer({
|
const VideoPlayerViewer({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -21,6 +22,7 @@ class VideoPlayerViewer extends HookConsumerWidget {
|
|||||||
required this.hideControlsTimer,
|
required this.hideControlsTimer,
|
||||||
required this.showControls,
|
required this.showControls,
|
||||||
required this.showDownloadingIndicator,
|
required this.showDownloadingIndicator,
|
||||||
|
required this.loopVideo,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -36,6 +38,7 @@ class VideoPlayerViewer extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
showControls: showControls && !isMotionVideo,
|
showControls: showControls && !isMotionVideo,
|
||||||
hideControlsTimer: hideControlsTimer,
|
hideControlsTimer: hideControlsTimer,
|
||||||
|
loopVideo: loopVideo,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Chewie(
|
return Chewie(
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class _LocationPicker extends HookWidget {
|
|||||||
).tr(),
|
).tr(),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => context.popRoute(latlng),
|
onPressed: () => context.maybePop(latlng),
|
||||||
child: Text(
|
child: Text(
|
||||||
"action_common_update",
|
"action_common_update",
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class _NonSelectionRow extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => context.popRoute(),
|
onPressed: () => context.maybePop(),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
shape: const CircleBorder(),
|
shape: const CircleBorder(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class MemoryBottomInfo extends StatelessWidget {
|
|||||||
MaterialButton(
|
MaterialButton(
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.popRoute();
|
context.maybePop();
|
||||||
scrollToDateNotifierProvider
|
scrollToDateNotifierProvider
|
||||||
.scrollToDate(memory.assets[0].fileCreatedAt);
|
.scrollToDate(memory.assets[0].fileCreatedAt);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
||||||
|
import 'video_viewer_settings.dart';
|
||||||
|
|
||||||
|
class AssetViewerSettings extends StatelessWidget {
|
||||||
|
const AssetViewerSettings({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final assetViewerSetting = [
|
||||||
|
const ImageViewerQualitySetting(),
|
||||||
|
const VideoViewerSettings(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return SettingsSubPageScaffold(
|
||||||
|
settings: assetViewerSetting,
|
||||||
|
showDivider: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
|
|
||||||
|
class ImageViewerQualitySetting extends HookConsumerWidget {
|
||||||
|
const ImageViewerQualitySetting({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final isPreview = useAppSettingsState(AppSettingsEnum.loadPreview);
|
||||||
|
final isOriginal = useAppSettingsState(AppSettingsEnum.loadOriginal);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SettingsSubTitle(title: "setting_image_viewer_title".tr()),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
title: Text(
|
||||||
|
'setting_image_viewer_help',
|
||||||
|
style: context.textTheme.bodyMedium,
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
valueNotifier: isPreview,
|
||||||
|
title: "setting_image_viewer_preview_title".tr(),
|
||||||
|
subtitle: "setting_image_viewer_preview_subtitle".tr(),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
|
),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
valueNotifier: isOriginal,
|
||||||
|
title: "setting_image_viewer_original_title".tr(),
|
||||||
|
subtitle: "setting_image_viewer_original_subtitle".tr(),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
|
|
||||||
|
class VideoViewerSettings extends HookConsumerWidget {
|
||||||
|
const VideoViewerSettings({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final useLoopVideo = useAppSettingsState(AppSettingsEnum.loopVideo);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SettingsSubTitle(title: "setting_video_viewer_title".tr()),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
valueNotifier: useLoopVideo,
|
||||||
|
title: "setting_video_viewer_looping_title".tr(),
|
||||||
|
subtitle: "setting_video_viewer_looping_subtitle".tr(),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
|
||||||
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
|
||||||
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
|
||||||
|
|
||||||
class ImageViewerQualitySetting extends HookWidget {
|
|
||||||
const ImageViewerQualitySetting({
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final isPreview = useAppSettingsState(AppSettingsEnum.loadPreview);
|
|
||||||
final isOriginal = useAppSettingsState(AppSettingsEnum.loadOriginal);
|
|
||||||
|
|
||||||
final viewerSettings = [
|
|
||||||
ListTile(
|
|
||||||
title: Text(
|
|
||||||
'setting_image_viewer_help',
|
|
||||||
style: context.textTheme.bodyMedium,
|
|
||||||
).tr(),
|
|
||||||
),
|
|
||||||
SettingsSwitchListTile(
|
|
||||||
valueNotifier: isPreview,
|
|
||||||
title: "setting_image_viewer_preview_title".tr(),
|
|
||||||
subtitle: "setting_image_viewer_preview_subtitle".tr(),
|
|
||||||
),
|
|
||||||
SettingsSwitchListTile(
|
|
||||||
valueNotifier: isOriginal,
|
|
||||||
title: "setting_image_viewer_original_title".tr(),
|
|
||||||
subtitle: "setting_image_viewer_original_subtitle".tr(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return SettingsSubPageScaffold(settings: viewerSettings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
+1
-1
@@ -3,7 +3,7 @@ Immich API
|
|||||||
|
|
||||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||||
|
|
||||||
- API version: 1.105.0
|
- API version: 1.105.1
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|||||||
+1
@@ -9,6 +9,7 @@ import 'package:openapi/api.dart';
|
|||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**accel** | [**TranscodeHWAccel**](TranscodeHWAccel.md) | |
|
**accel** | [**TranscodeHWAccel**](TranscodeHWAccel.md) | |
|
||||||
|
**accelDecode** | **bool** | |
|
||||||
**acceptedAudioCodecs** | [**List<AudioCodec>**](AudioCodec.md) | | [default to const []]
|
**acceptedAudioCodecs** | [**List<AudioCodec>**](AudioCodec.md) | | [default to const []]
|
||||||
**acceptedVideoCodecs** | [**List<VideoCodec>**](VideoCodec.md) | | [default to const []]
|
**acceptedVideoCodecs** | [**List<VideoCodec>**](VideoCodec.md) | | [default to const []]
|
||||||
**bframes** | **int** | |
|
**bframes** | **int** | |
|
||||||
|
|||||||
+9
-1
@@ -14,6 +14,7 @@ class SystemConfigFFmpegDto {
|
|||||||
/// Returns a new [SystemConfigFFmpegDto] instance.
|
/// Returns a new [SystemConfigFFmpegDto] instance.
|
||||||
SystemConfigFFmpegDto({
|
SystemConfigFFmpegDto({
|
||||||
required this.accel,
|
required this.accel,
|
||||||
|
required this.accelDecode,
|
||||||
this.acceptedAudioCodecs = const [],
|
this.acceptedAudioCodecs = const [],
|
||||||
this.acceptedVideoCodecs = const [],
|
this.acceptedVideoCodecs = const [],
|
||||||
required this.bframes,
|
required this.bframes,
|
||||||
@@ -37,6 +38,8 @@ class SystemConfigFFmpegDto {
|
|||||||
|
|
||||||
TranscodeHWAccel accel;
|
TranscodeHWAccel accel;
|
||||||
|
|
||||||
|
bool accelDecode;
|
||||||
|
|
||||||
List<AudioCodec> acceptedAudioCodecs;
|
List<AudioCodec> acceptedAudioCodecs;
|
||||||
|
|
||||||
List<VideoCodec> acceptedVideoCodecs;
|
List<VideoCodec> acceptedVideoCodecs;
|
||||||
@@ -87,6 +90,7 @@ class SystemConfigFFmpegDto {
|
|||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegDto &&
|
bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegDto &&
|
||||||
other.accel == accel &&
|
other.accel == accel &&
|
||||||
|
other.accelDecode == accelDecode &&
|
||||||
_deepEquality.equals(other.acceptedAudioCodecs, acceptedAudioCodecs) &&
|
_deepEquality.equals(other.acceptedAudioCodecs, acceptedAudioCodecs) &&
|
||||||
_deepEquality.equals(other.acceptedVideoCodecs, acceptedVideoCodecs) &&
|
_deepEquality.equals(other.acceptedVideoCodecs, acceptedVideoCodecs) &&
|
||||||
other.bframes == bframes &&
|
other.bframes == bframes &&
|
||||||
@@ -111,6 +115,7 @@ class SystemConfigFFmpegDto {
|
|||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(accel.hashCode) +
|
(accel.hashCode) +
|
||||||
|
(accelDecode.hashCode) +
|
||||||
(acceptedAudioCodecs.hashCode) +
|
(acceptedAudioCodecs.hashCode) +
|
||||||
(acceptedVideoCodecs.hashCode) +
|
(acceptedVideoCodecs.hashCode) +
|
||||||
(bframes.hashCode) +
|
(bframes.hashCode) +
|
||||||
@@ -132,11 +137,12 @@ class SystemConfigFFmpegDto {
|
|||||||
(twoPass.hashCode);
|
(twoPass.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'SystemConfigFFmpegDto[accel=$accel, acceptedAudioCodecs=$acceptedAudioCodecs, acceptedVideoCodecs=$acceptedVideoCodecs, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, npl=$npl, preferredHwDevice=$preferredHwDevice, preset=$preset, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]';
|
String toString() => 'SystemConfigFFmpegDto[accel=$accel, accelDecode=$accelDecode, acceptedAudioCodecs=$acceptedAudioCodecs, acceptedVideoCodecs=$acceptedVideoCodecs, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, npl=$npl, preferredHwDevice=$preferredHwDevice, preset=$preset, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'accel'] = this.accel;
|
json[r'accel'] = this.accel;
|
||||||
|
json[r'accelDecode'] = this.accelDecode;
|
||||||
json[r'acceptedAudioCodecs'] = this.acceptedAudioCodecs;
|
json[r'acceptedAudioCodecs'] = this.acceptedAudioCodecs;
|
||||||
json[r'acceptedVideoCodecs'] = this.acceptedVideoCodecs;
|
json[r'acceptedVideoCodecs'] = this.acceptedVideoCodecs;
|
||||||
json[r'bframes'] = this.bframes;
|
json[r'bframes'] = this.bframes;
|
||||||
@@ -168,6 +174,7 @@ class SystemConfigFFmpegDto {
|
|||||||
|
|
||||||
return SystemConfigFFmpegDto(
|
return SystemConfigFFmpegDto(
|
||||||
accel: TranscodeHWAccel.fromJson(json[r'accel'])!,
|
accel: TranscodeHWAccel.fromJson(json[r'accel'])!,
|
||||||
|
accelDecode: mapValueOfType<bool>(json, r'accelDecode')!,
|
||||||
acceptedAudioCodecs: AudioCodec.listFromJson(json[r'acceptedAudioCodecs']),
|
acceptedAudioCodecs: AudioCodec.listFromJson(json[r'acceptedAudioCodecs']),
|
||||||
acceptedVideoCodecs: VideoCodec.listFromJson(json[r'acceptedVideoCodecs']),
|
acceptedVideoCodecs: VideoCodec.listFromJson(json[r'acceptedVideoCodecs']),
|
||||||
bframes: mapValueOfType<int>(json, r'bframes')!,
|
bframes: mapValueOfType<int>(json, r'bframes')!,
|
||||||
@@ -235,6 +242,7 @@ class SystemConfigFFmpegDto {
|
|||||||
/// The list of required keys that must be present in a JSON.
|
/// The list of required keys that must be present in a JSON.
|
||||||
static const requiredKeys = <String>{
|
static const requiredKeys = <String>{
|
||||||
'accel',
|
'accel',
|
||||||
|
'accelDecode',
|
||||||
'acceptedAudioCodecs',
|
'acceptedAudioCodecs',
|
||||||
'acceptedVideoCodecs',
|
'acceptedVideoCodecs',
|
||||||
'bframes',
|
'bframes',
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// bool accelDecode
|
||||||
|
test('to test the property `accelDecode`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// List<AudioCodec> acceptedAudioCodecs (default value: const [])
|
// List<AudioCodec> acceptedAudioCodecs (default value: const [])
|
||||||
test('to test the property `acceptedAudioCodecs`', () async {
|
test('to test the property `acceptedAudioCodecs`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
|||||||
+8
-8
@@ -61,18 +61,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: auto_route
|
name: auto_route
|
||||||
sha256: "82f8df1d177416bc6b7a449127d0270ff1f0f633a91f2ceb7a85d4f07c3affa1"
|
sha256: "6cad3f408863ffff2b5757967c802b18415dac4acb1b40c5cdd45d0a26e5080f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.8.4"
|
version: "8.1.3"
|
||||||
auto_route_generator:
|
auto_route_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: auto_route_generator
|
name: auto_route_generator
|
||||||
sha256: "11067a3bcd643812518fe26c0c9ec073990286cabfd9d74b6da9ef9b913c4d22"
|
sha256: ba28133d3a3bf0a66772bcc98dade5843753cd9f1a8fb4802b842895515b67d3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.3.2"
|
version: "8.0.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -503,10 +503,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
|
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "4.0.0"
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -896,10 +896,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.0.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
+4
-4
@@ -2,7 +2,7 @@ name: immich_mobile
|
|||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.105.0+139
|
version: 1.105.1+140
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.0 <4.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
@@ -22,7 +22,7 @@ dependencies:
|
|||||||
cached_network_image: ^3.3.1
|
cached_network_image: ^3.3.1
|
||||||
flutter_cache_manager: ^3.3.1
|
flutter_cache_manager: ^3.3.1
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
auto_route: ^7.8.4
|
auto_route: ^8.0.2
|
||||||
fluttertoast: ^8.2.4
|
fluttertoast: ^8.2.4
|
||||||
video_player: ^2.8.2
|
video_player: ^2.8.2
|
||||||
chewie: ^1.7.4
|
chewie: ^1.7.4
|
||||||
@@ -86,9 +86,9 @@ dependency_overrides:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^3.0.1
|
flutter_lints: ^4.0.0
|
||||||
build_runner: ^2.4.8
|
build_runner: ^2.4.8
|
||||||
auto_route_generator: ^7.3.2
|
auto_route_generator: ^8.0.0
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.13.1
|
||||||
flutter_native_splash: ^2.3.9
|
flutter_native_splash: ^2.3.9
|
||||||
isar_generator: ^3.1.0+1
|
isar_generator: ^3.1.0+1
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@Skip('currently failing due to mock HTTP client to download ISAR binaries')
|
@Skip('currently failing due to mock HTTP client to download ISAR binaries')
|
||||||
@Tags(['widget'])
|
@Tags(['widget'])
|
||||||
|
library;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@Skip('currently failing due to mock HTTP client to download ISAR binaries')
|
@Skip('currently failing due to mock HTTP client to download ISAR binaries')
|
||||||
@Tags(['widget'])
|
@Tags(['widget'])
|
||||||
|
library;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@Skip('currently failing due to mock HTTP client to download ISAR binaries')
|
@Skip('currently failing due to mock HTTP client to download ISAR binaries')
|
||||||
@Tags(['widget'])
|
@Tags(['widget'])
|
||||||
|
library;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@Tags(['widget'])
|
@Tags(['widget'])
|
||||||
|
library;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@Tags(['widget'])
|
@Tags(['widget'])
|
||||||
|
library;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|||||||
@@ -6446,7 +6446,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
@@ -10002,6 +10002,9 @@
|
|||||||
"accel": {
|
"accel": {
|
||||||
"$ref": "#/components/schemas/TranscodeHWAccel"
|
"$ref": "#/components/schemas/TranscodeHWAccel"
|
||||||
},
|
},
|
||||||
|
"accelDecode": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"acceptedAudioCodecs": {
|
"acceptedAudioCodecs": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/AudioCodec"
|
"$ref": "#/components/schemas/AudioCodec"
|
||||||
@@ -10077,6 +10080,7 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"accel",
|
"accel",
|
||||||
|
"accelDecode",
|
||||||
"acceptedAudioCodecs",
|
"acceptedAudioCodecs",
|
||||||
"acceptedVideoCodecs",
|
"acceptedVideoCodecs",
|
||||||
"bframes",
|
"bframes",
|
||||||
|
|||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 1.105.0
|
* 1.105.1
|
||||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||||
* See https://www.npmjs.com/package/oazapfts
|
* See https://www.npmjs.com/package/oazapfts
|
||||||
*/
|
*/
|
||||||
@@ -863,6 +863,7 @@ export type AssetFullSyncDto = {
|
|||||||
};
|
};
|
||||||
export type SystemConfigFFmpegDto = {
|
export type SystemConfigFFmpegDto = {
|
||||||
accel: TranscodeHWAccel;
|
accel: TranscodeHWAccel;
|
||||||
|
accelDecode: boolean;
|
||||||
acceptedAudioCodecs: AudioCodec[];
|
acceptedAudioCodecs: AudioCodec[];
|
||||||
acceptedVideoCodecs: VideoCodec[];
|
acceptedVideoCodecs: VideoCodec[];
|
||||||
bframes: number;
|
bframes: number;
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/bullmq": "^10.0.1",
|
"@nestjs/bullmq": "^10.0.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.105.0",
|
"version": "1.105.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
+336
-2
@@ -1,12 +1,346 @@
|
|||||||
import { RegisterQueueOptions } from '@nestjs/bullmq';
|
import { RegisterQueueOptions } from '@nestjs/bullmq';
|
||||||
import { ConfigModuleOptions } from '@nestjs/config';
|
import { ConfigModuleOptions } from '@nestjs/config';
|
||||||
|
import { CronExpression } from '@nestjs/schedule';
|
||||||
import { QueueOptions } from 'bullmq';
|
import { QueueOptions } from 'bullmq';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { RedisOptions } from 'ioredis';
|
import { RedisOptions } from 'ioredis';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import { CLS_ID, ClsModuleOptions } from 'nestjs-cls';
|
import { CLS_ID, ClsModuleOptions } from 'nestjs-cls';
|
||||||
import { LogLevel } from 'src/entities/system-config.entity';
|
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface';
|
||||||
import { QueueName } from 'src/interfaces/job.interface';
|
|
||||||
|
export enum TranscodePolicy {
|
||||||
|
ALL = 'all',
|
||||||
|
OPTIMAL = 'optimal',
|
||||||
|
BITRATE = 'bitrate',
|
||||||
|
REQUIRED = 'required',
|
||||||
|
DISABLED = 'disabled',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TranscodeTarget {
|
||||||
|
NONE,
|
||||||
|
AUDIO,
|
||||||
|
VIDEO,
|
||||||
|
ALL,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VideoCodec {
|
||||||
|
H264 = 'h264',
|
||||||
|
HEVC = 'hevc',
|
||||||
|
VP9 = 'vp9',
|
||||||
|
AV1 = 'av1',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AudioCodec {
|
||||||
|
MP3 = 'mp3',
|
||||||
|
AAC = 'aac',
|
||||||
|
LIBOPUS = 'libopus',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TranscodeHWAccel {
|
||||||
|
NVENC = 'nvenc',
|
||||||
|
QSV = 'qsv',
|
||||||
|
VAAPI = 'vaapi',
|
||||||
|
RKMPP = 'rkmpp',
|
||||||
|
DISABLED = 'disabled',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ToneMapping {
|
||||||
|
HABLE = 'hable',
|
||||||
|
MOBIUS = 'mobius',
|
||||||
|
REINHARD = 'reinhard',
|
||||||
|
DISABLED = 'disabled',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CQMode {
|
||||||
|
AUTO = 'auto',
|
||||||
|
CQP = 'cqp',
|
||||||
|
ICQ = 'icq',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Colorspace {
|
||||||
|
SRGB = 'srgb',
|
||||||
|
P3 = 'p3',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ImageFormat {
|
||||||
|
JPEG = 'jpeg',
|
||||||
|
WEBP = 'webp',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LogLevel {
|
||||||
|
VERBOSE = 'verbose',
|
||||||
|
DEBUG = 'debug',
|
||||||
|
LOG = 'log',
|
||||||
|
WARN = 'warn',
|
||||||
|
ERROR = 'error',
|
||||||
|
FATAL = 'fatal',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SystemConfig {
|
||||||
|
ffmpeg: {
|
||||||
|
crf: number;
|
||||||
|
threads: number;
|
||||||
|
preset: string;
|
||||||
|
targetVideoCodec: VideoCodec;
|
||||||
|
acceptedVideoCodecs: VideoCodec[];
|
||||||
|
targetAudioCodec: AudioCodec;
|
||||||
|
acceptedAudioCodecs: AudioCodec[];
|
||||||
|
targetResolution: string;
|
||||||
|
maxBitrate: string;
|
||||||
|
bframes: number;
|
||||||
|
refs: number;
|
||||||
|
gopSize: number;
|
||||||
|
npl: number;
|
||||||
|
temporalAQ: boolean;
|
||||||
|
cqMode: CQMode;
|
||||||
|
twoPass: boolean;
|
||||||
|
preferredHwDevice: string;
|
||||||
|
transcode: TranscodePolicy;
|
||||||
|
accel: TranscodeHWAccel;
|
||||||
|
accelDecode: false;
|
||||||
|
tonemap: ToneMapping;
|
||||||
|
};
|
||||||
|
job: Record<ConcurrentQueueName, { concurrency: number }>;
|
||||||
|
logging: {
|
||||||
|
enabled: boolean;
|
||||||
|
level: LogLevel;
|
||||||
|
};
|
||||||
|
machineLearning: {
|
||||||
|
enabled: boolean;
|
||||||
|
url: string;
|
||||||
|
clip: {
|
||||||
|
enabled: boolean;
|
||||||
|
modelName: string;
|
||||||
|
};
|
||||||
|
facialRecognition: {
|
||||||
|
enabled: boolean;
|
||||||
|
modelName: string;
|
||||||
|
minScore: number;
|
||||||
|
minFaces: number;
|
||||||
|
maxDistance: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
map: {
|
||||||
|
enabled: boolean;
|
||||||
|
lightStyle: string;
|
||||||
|
darkStyle: string;
|
||||||
|
};
|
||||||
|
reverseGeocoding: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
oauth: {
|
||||||
|
autoLaunch: boolean;
|
||||||
|
autoRegister: boolean;
|
||||||
|
buttonText: string;
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
defaultStorageQuota: number;
|
||||||
|
enabled: boolean;
|
||||||
|
issuerUrl: string;
|
||||||
|
mobileOverrideEnabled: boolean;
|
||||||
|
mobileRedirectUri: string;
|
||||||
|
scope: string;
|
||||||
|
signingAlgorithm: string;
|
||||||
|
storageLabelClaim: string;
|
||||||
|
storageQuotaClaim: string;
|
||||||
|
};
|
||||||
|
passwordLogin: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
storageTemplate: {
|
||||||
|
enabled: boolean;
|
||||||
|
hashVerificationEnabled: boolean;
|
||||||
|
template: string;
|
||||||
|
};
|
||||||
|
image: {
|
||||||
|
thumbnailFormat: ImageFormat;
|
||||||
|
thumbnailSize: number;
|
||||||
|
previewFormat: ImageFormat;
|
||||||
|
previewSize: number;
|
||||||
|
quality: number;
|
||||||
|
colorspace: Colorspace;
|
||||||
|
extractEmbedded: boolean;
|
||||||
|
};
|
||||||
|
newVersionCheck: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
trash: {
|
||||||
|
enabled: boolean;
|
||||||
|
days: number;
|
||||||
|
};
|
||||||
|
theme: {
|
||||||
|
customCss: string;
|
||||||
|
};
|
||||||
|
library: {
|
||||||
|
scan: {
|
||||||
|
enabled: boolean;
|
||||||
|
cronExpression: string;
|
||||||
|
};
|
||||||
|
watch: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
notifications: {
|
||||||
|
smtp: {
|
||||||
|
enabled: boolean;
|
||||||
|
from: string;
|
||||||
|
replyTo: string;
|
||||||
|
transport: {
|
||||||
|
ignoreCert: boolean;
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
server: {
|
||||||
|
externalDomain: string;
|
||||||
|
loginPageMessage: string;
|
||||||
|
};
|
||||||
|
user: {
|
||||||
|
deleteDelay: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaults = Object.freeze<SystemConfig>({
|
||||||
|
ffmpeg: {
|
||||||
|
crf: 23,
|
||||||
|
threads: 0,
|
||||||
|
preset: 'ultrafast',
|
||||||
|
targetVideoCodec: VideoCodec.H264,
|
||||||
|
acceptedVideoCodecs: [VideoCodec.H264],
|
||||||
|
targetAudioCodec: AudioCodec.AAC,
|
||||||
|
acceptedAudioCodecs: [AudioCodec.AAC, AudioCodec.MP3, AudioCodec.LIBOPUS],
|
||||||
|
targetResolution: '720',
|
||||||
|
maxBitrate: '0',
|
||||||
|
bframes: -1,
|
||||||
|
refs: 0,
|
||||||
|
gopSize: 0,
|
||||||
|
npl: 0,
|
||||||
|
temporalAQ: false,
|
||||||
|
cqMode: CQMode.AUTO,
|
||||||
|
twoPass: false,
|
||||||
|
preferredHwDevice: 'auto',
|
||||||
|
transcode: TranscodePolicy.REQUIRED,
|
||||||
|
tonemap: ToneMapping.HABLE,
|
||||||
|
accel: TranscodeHWAccel.DISABLED,
|
||||||
|
accelDecode: false,
|
||||||
|
},
|
||||||
|
job: {
|
||||||
|
[QueueName.BACKGROUND_TASK]: { concurrency: 5 },
|
||||||
|
[QueueName.SMART_SEARCH]: { concurrency: 2 },
|
||||||
|
[QueueName.METADATA_EXTRACTION]: { concurrency: 5 },
|
||||||
|
[QueueName.FACE_DETECTION]: { concurrency: 2 },
|
||||||
|
[QueueName.SEARCH]: { concurrency: 5 },
|
||||||
|
[QueueName.SIDECAR]: { concurrency: 5 },
|
||||||
|
[QueueName.LIBRARY]: { concurrency: 5 },
|
||||||
|
[QueueName.MIGRATION]: { concurrency: 5 },
|
||||||
|
[QueueName.THUMBNAIL_GENERATION]: { concurrency: 5 },
|
||||||
|
[QueueName.VIDEO_CONVERSION]: { concurrency: 1 },
|
||||||
|
[QueueName.NOTIFICATION]: { concurrency: 5 },
|
||||||
|
},
|
||||||
|
logging: {
|
||||||
|
enabled: true,
|
||||||
|
level: LogLevel.LOG,
|
||||||
|
},
|
||||||
|
machineLearning: {
|
||||||
|
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
|
||||||
|
url: process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003',
|
||||||
|
clip: {
|
||||||
|
enabled: true,
|
||||||
|
modelName: 'ViT-B-32__openai',
|
||||||
|
},
|
||||||
|
facialRecognition: {
|
||||||
|
enabled: true,
|
||||||
|
modelName: 'buffalo_l',
|
||||||
|
minScore: 0.7,
|
||||||
|
maxDistance: 0.5,
|
||||||
|
minFaces: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map: {
|
||||||
|
enabled: true,
|
||||||
|
lightStyle: '',
|
||||||
|
darkStyle: '',
|
||||||
|
},
|
||||||
|
reverseGeocoding: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
oauth: {
|
||||||
|
autoLaunch: false,
|
||||||
|
autoRegister: true,
|
||||||
|
buttonText: 'Login with OAuth',
|
||||||
|
clientId: '',
|
||||||
|
clientSecret: '',
|
||||||
|
defaultStorageQuota: 0,
|
||||||
|
enabled: false,
|
||||||
|
issuerUrl: '',
|
||||||
|
mobileOverrideEnabled: false,
|
||||||
|
mobileRedirectUri: '',
|
||||||
|
scope: 'openid email profile',
|
||||||
|
signingAlgorithm: 'RS256',
|
||||||
|
storageLabelClaim: 'preferred_username',
|
||||||
|
storageQuotaClaim: 'immich_quota',
|
||||||
|
},
|
||||||
|
passwordLogin: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
storageTemplate: {
|
||||||
|
enabled: false,
|
||||||
|
hashVerificationEnabled: true,
|
||||||
|
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
thumbnailFormat: ImageFormat.WEBP,
|
||||||
|
thumbnailSize: 250,
|
||||||
|
previewFormat: ImageFormat.JPEG,
|
||||||
|
previewSize: 1440,
|
||||||
|
quality: 80,
|
||||||
|
colorspace: Colorspace.P3,
|
||||||
|
extractEmbedded: false,
|
||||||
|
},
|
||||||
|
newVersionCheck: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
trash: {
|
||||||
|
enabled: true,
|
||||||
|
days: 30,
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
customCss: '',
|
||||||
|
},
|
||||||
|
library: {
|
||||||
|
scan: {
|
||||||
|
enabled: true,
|
||||||
|
cronExpression: CronExpression.EVERY_DAY_AT_MIDNIGHT,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
externalDomain: '',
|
||||||
|
loginPageMessage: '',
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
smtp: {
|
||||||
|
enabled: false,
|
||||||
|
from: '',
|
||||||
|
replyTo: '',
|
||||||
|
transport: {
|
||||||
|
ignoreCert: false,
|
||||||
|
host: '',
|
||||||
|
port: 587,
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
deleteDelay: 7,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const WHEN_DB_URL_SET = Joi.when('DB_URL', {
|
const WHEN_DB_URL_SET = Joi.when('DB_URL', {
|
||||||
is: Joi.exist(),
|
is: Joi.exist(),
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { dirname, join, resolve } from 'node:path';
|
import { dirname, join, resolve } from 'node:path';
|
||||||
|
import { ImageFormat } from 'src/config';
|
||||||
import { APP_MEDIA_LOCATION } from 'src/constants';
|
import { APP_MEDIA_LOCATION } from 'src/constants';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { AssetPathType, PathType, PersonPathType } from 'src/entities/move.entity';
|
import { AssetPathType, PathType, PersonPathType } from 'src/entities/move.entity';
|
||||||
import { PersonEntity } from 'src/entities/person.entity';
|
import { PersonEntity } from 'src/entities/person.entity';
|
||||||
import { ImageFormat } from 'src/entities/system-config.entity';
|
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
|
|||||||
@@ -1,189 +1,19 @@
|
|||||||
import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { CronExpression } from '@nestjs/schedule';
|
|
||||||
import AsyncLock from 'async-lock';
|
import AsyncLock from 'async-lock';
|
||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
import { validate } from 'class-validator';
|
||||||
import { load as loadYaml } from 'js-yaml';
|
import { load as loadYaml } from 'js-yaml';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
import { SystemConfig, defaults } from 'src/config';
|
||||||
import { SystemConfigDto } from 'src/dtos/system-config.dto';
|
import { SystemConfigDto } from 'src/dtos/system-config.dto';
|
||||||
import {
|
import { SystemConfigEntity, SystemConfigKey, SystemConfigValue } from 'src/entities/system-config.entity';
|
||||||
AudioCodec,
|
|
||||||
CQMode,
|
|
||||||
Colorspace,
|
|
||||||
ImageFormat,
|
|
||||||
LogLevel,
|
|
||||||
SystemConfig,
|
|
||||||
SystemConfigEntity,
|
|
||||||
SystemConfigKey,
|
|
||||||
SystemConfigValue,
|
|
||||||
ToneMapping,
|
|
||||||
TranscodeHWAccel,
|
|
||||||
TranscodePolicy,
|
|
||||||
VideoCodec,
|
|
||||||
} from 'src/entities/system-config.entity';
|
|
||||||
import { DatabaseLock } from 'src/interfaces/database.interface';
|
import { DatabaseLock } from 'src/interfaces/database.interface';
|
||||||
import { QueueName } from 'src/interfaces/job.interface';
|
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
|
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
|
||||||
|
|
||||||
export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
|
export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
|
||||||
|
|
||||||
export const defaults = Object.freeze<SystemConfig>({
|
|
||||||
ffmpeg: {
|
|
||||||
crf: 23,
|
|
||||||
threads: 0,
|
|
||||||
preset: 'ultrafast',
|
|
||||||
targetVideoCodec: VideoCodec.H264,
|
|
||||||
acceptedVideoCodecs: [VideoCodec.H264],
|
|
||||||
targetAudioCodec: AudioCodec.AAC,
|
|
||||||
acceptedAudioCodecs: [AudioCodec.AAC, AudioCodec.MP3, AudioCodec.LIBOPUS],
|
|
||||||
targetResolution: '720',
|
|
||||||
maxBitrate: '0',
|
|
||||||
bframes: -1,
|
|
||||||
refs: 0,
|
|
||||||
gopSize: 0,
|
|
||||||
npl: 0,
|
|
||||||
temporalAQ: false,
|
|
||||||
cqMode: CQMode.AUTO,
|
|
||||||
twoPass: false,
|
|
||||||
preferredHwDevice: 'auto',
|
|
||||||
transcode: TranscodePolicy.REQUIRED,
|
|
||||||
tonemap: ToneMapping.HABLE,
|
|
||||||
accel: TranscodeHWAccel.DISABLED,
|
|
||||||
},
|
|
||||||
job: {
|
|
||||||
[QueueName.BACKGROUND_TASK]: { concurrency: 5 },
|
|
||||||
[QueueName.SMART_SEARCH]: { concurrency: 2 },
|
|
||||||
[QueueName.METADATA_EXTRACTION]: { concurrency: 5 },
|
|
||||||
[QueueName.FACE_DETECTION]: { concurrency: 2 },
|
|
||||||
[QueueName.SEARCH]: { concurrency: 5 },
|
|
||||||
[QueueName.SIDECAR]: { concurrency: 5 },
|
|
||||||
[QueueName.LIBRARY]: { concurrency: 5 },
|
|
||||||
[QueueName.MIGRATION]: { concurrency: 5 },
|
|
||||||
[QueueName.THUMBNAIL_GENERATION]: { concurrency: 5 },
|
|
||||||
[QueueName.VIDEO_CONVERSION]: { concurrency: 1 },
|
|
||||||
[QueueName.NOTIFICATION]: { concurrency: 5 },
|
|
||||||
},
|
|
||||||
logging: {
|
|
||||||
enabled: true,
|
|
||||||
level: LogLevel.LOG,
|
|
||||||
},
|
|
||||||
machineLearning: {
|
|
||||||
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
|
|
||||||
url: process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003',
|
|
||||||
clip: {
|
|
||||||
enabled: true,
|
|
||||||
modelName: 'ViT-B-32__openai',
|
|
||||||
},
|
|
||||||
facialRecognition: {
|
|
||||||
enabled: true,
|
|
||||||
modelName: 'buffalo_l',
|
|
||||||
minScore: 0.7,
|
|
||||||
maxDistance: 0.5,
|
|
||||||
minFaces: 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map: {
|
|
||||||
enabled: true,
|
|
||||||
lightStyle: '',
|
|
||||||
darkStyle: '',
|
|
||||||
},
|
|
||||||
reverseGeocoding: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
oauth: {
|
|
||||||
autoLaunch: false,
|
|
||||||
autoRegister: true,
|
|
||||||
buttonText: 'Login with OAuth',
|
|
||||||
clientId: '',
|
|
||||||
clientSecret: '',
|
|
||||||
defaultStorageQuota: 0,
|
|
||||||
enabled: false,
|
|
||||||
issuerUrl: '',
|
|
||||||
mobileOverrideEnabled: false,
|
|
||||||
mobileRedirectUri: '',
|
|
||||||
scope: 'openid email profile',
|
|
||||||
signingAlgorithm: 'RS256',
|
|
||||||
storageLabelClaim: 'preferred_username',
|
|
||||||
storageQuotaClaim: 'immich_quota',
|
|
||||||
},
|
|
||||||
passwordLogin: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
storageTemplate: {
|
|
||||||
enabled: false,
|
|
||||||
hashVerificationEnabled: true,
|
|
||||||
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
thumbnailFormat: ImageFormat.WEBP,
|
|
||||||
thumbnailSize: 250,
|
|
||||||
previewFormat: ImageFormat.JPEG,
|
|
||||||
previewSize: 1440,
|
|
||||||
quality: 80,
|
|
||||||
colorspace: Colorspace.P3,
|
|
||||||
extractEmbedded: false,
|
|
||||||
},
|
|
||||||
newVersionCheck: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
trash: {
|
|
||||||
enabled: true,
|
|
||||||
days: 30,
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
customCss: '',
|
|
||||||
},
|
|
||||||
library: {
|
|
||||||
scan: {
|
|
||||||
enabled: true,
|
|
||||||
cronExpression: CronExpression.EVERY_DAY_AT_MIDNIGHT,
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
externalDomain: '',
|
|
||||||
loginPageMessage: '',
|
|
||||||
},
|
|
||||||
notifications: {
|
|
||||||
smtp: {
|
|
||||||
enabled: false,
|
|
||||||
from: '',
|
|
||||||
replyTo: '',
|
|
||||||
transport: {
|
|
||||||
ignoreCert: false,
|
|
||||||
host: '',
|
|
||||||
port: 587,
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
deleteDelay: 7,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export enum FeatureFlag {
|
|
||||||
SMART_SEARCH = 'smartSearch',
|
|
||||||
FACIAL_RECOGNITION = 'facialRecognition',
|
|
||||||
MAP = 'map',
|
|
||||||
REVERSE_GEOCODING = 'reverseGeocoding',
|
|
||||||
SIDECAR = 'sidecar',
|
|
||||||
SEARCH = 'search',
|
|
||||||
OAUTH = 'oauth',
|
|
||||||
OAUTH_AUTO_LAUNCH = 'oauthAutoLaunch',
|
|
||||||
PASSWORD_LOGIN = 'passwordLogin',
|
|
||||||
CONFIG_FILE = 'configFile',
|
|
||||||
TRASH = 'trash',
|
|
||||||
EMAIL = 'email',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FeatureFlags = Record<FeatureFlag, boolean>;
|
|
||||||
|
|
||||||
let instance: SystemConfigCore | null;
|
let instance: SystemConfigCore | null;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -192,7 +22,7 @@ export class SystemConfigCore {
|
|||||||
private config: SystemConfig | null = null;
|
private config: SystemConfig | null = null;
|
||||||
private lastUpdated: number | null = null;
|
private lastUpdated: number | null = null;
|
||||||
|
|
||||||
public config$ = new Subject<SystemConfig>();
|
config$ = new Subject<SystemConfig>();
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
private repository: ISystemConfigRepository,
|
private repository: ISystemConfigRepository,
|
||||||
@@ -210,68 +40,7 @@ export class SystemConfigCore {
|
|||||||
instance = null;
|
instance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async requireFeature(feature: FeatureFlag) {
|
async getConfig(force = false): Promise<SystemConfig> {
|
||||||
const hasFeature = await this.hasFeature(feature);
|
|
||||||
if (!hasFeature) {
|
|
||||||
switch (feature) {
|
|
||||||
case FeatureFlag.SMART_SEARCH: {
|
|
||||||
throw new BadRequestException('Smart search is not enabled');
|
|
||||||
}
|
|
||||||
case FeatureFlag.FACIAL_RECOGNITION: {
|
|
||||||
throw new BadRequestException('Facial recognition is not enabled');
|
|
||||||
}
|
|
||||||
case FeatureFlag.SIDECAR: {
|
|
||||||
throw new BadRequestException('Sidecar is not enabled');
|
|
||||||
}
|
|
||||||
case FeatureFlag.SEARCH: {
|
|
||||||
throw new BadRequestException('Search is not enabled');
|
|
||||||
}
|
|
||||||
case FeatureFlag.OAUTH: {
|
|
||||||
throw new BadRequestException('OAuth is not enabled');
|
|
||||||
}
|
|
||||||
case FeatureFlag.PASSWORD_LOGIN: {
|
|
||||||
throw new BadRequestException('Password login is not enabled');
|
|
||||||
}
|
|
||||||
case FeatureFlag.CONFIG_FILE: {
|
|
||||||
throw new BadRequestException('Config file is not set');
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new ForbiddenException(`Missing required feature: ${feature}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async hasFeature(feature: FeatureFlag) {
|
|
||||||
const features = await this.getFeatures();
|
|
||||||
return features[feature] ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFeatures(): Promise<FeatureFlags> {
|
|
||||||
const config = await this.getConfig();
|
|
||||||
const mlEnabled = config.machineLearning.enabled;
|
|
||||||
|
|
||||||
return {
|
|
||||||
[FeatureFlag.SMART_SEARCH]: mlEnabled && config.machineLearning.clip.enabled,
|
|
||||||
[FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled,
|
|
||||||
[FeatureFlag.MAP]: config.map.enabled,
|
|
||||||
[FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled,
|
|
||||||
[FeatureFlag.SIDECAR]: true,
|
|
||||||
[FeatureFlag.SEARCH]: true,
|
|
||||||
[FeatureFlag.TRASH]: config.trash.enabled,
|
|
||||||
[FeatureFlag.OAUTH]: config.oauth.enabled,
|
|
||||||
[FeatureFlag.OAUTH_AUTO_LAUNCH]: config.oauth.autoLaunch,
|
|
||||||
[FeatureFlag.PASSWORD_LOGIN]: config.passwordLogin.enabled,
|
|
||||||
[FeatureFlag.CONFIG_FILE]: !!process.env.IMMICH_CONFIG_FILE,
|
|
||||||
[FeatureFlag.EMAIL]: config.notifications.smtp.enabled,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDefaults(): SystemConfig {
|
|
||||||
return defaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getConfig(force = false): Promise<SystemConfig> {
|
|
||||||
if (force || !this.config) {
|
if (force || !this.config) {
|
||||||
const lastUpdated = this.lastUpdated;
|
const lastUpdated = this.lastUpdated;
|
||||||
await this.asyncLock.acquire(DatabaseLock[DatabaseLock.GetSystemConfig], async () => {
|
await this.asyncLock.acquire(DatabaseLock[DatabaseLock.GetSystemConfig], async () => {
|
||||||
@@ -285,11 +54,7 @@ export class SystemConfigCore {
|
|||||||
return this.config!;
|
return this.config!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateConfig(newConfig: SystemConfig): Promise<SystemConfig> {
|
async updateConfig(newConfig: SystemConfig): Promise<SystemConfig> {
|
||||||
if (await this.hasFeature(FeatureFlag.CONFIG_FILE)) {
|
|
||||||
throw new BadRequestException('Cannot update configuration while IMMICH_CONFIG_FILE is in use');
|
|
||||||
}
|
|
||||||
|
|
||||||
const updates: SystemConfigEntity[] = [];
|
const updates: SystemConfigEntity[] = [];
|
||||||
const deletes: SystemConfigEntity[] = [];
|
const deletes: SystemConfigEntity[] = [];
|
||||||
|
|
||||||
@@ -328,16 +93,19 @@ export class SystemConfigCore {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async refreshConfig() {
|
async refreshConfig() {
|
||||||
const newConfig = await this.getConfig(true);
|
const newConfig = await this.getConfig(true);
|
||||||
|
|
||||||
this.config$.next(newConfig);
|
this.config$.next(newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUsingConfigFile() {
|
||||||
|
return !!process.env.IMMICH_CONFIG_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
private async buildConfig() {
|
private async buildConfig() {
|
||||||
const config = _.cloneDeep(defaults);
|
const config = _.cloneDeep(defaults);
|
||||||
const overrides = process.env.IMMICH_CONFIG_FILE
|
const overrides = this.isUsingConfigFile()
|
||||||
? await this.loadFromFile(process.env.IMMICH_CONFIG_FILE)
|
? await this.loadFromFile(process.env.IMMICH_CONFIG_FILE as string)
|
||||||
: await this.repository.load();
|
: await this.repository.load();
|
||||||
|
|
||||||
for (const { key, value } of overrides) {
|
for (const { key, value } of overrides) {
|
||||||
@@ -347,7 +115,7 @@ export class SystemConfigCore {
|
|||||||
|
|
||||||
const errors = await validate(plainToInstance(SystemConfigDto, config));
|
const errors = await validate(plainToInstance(SystemConfigDto, config));
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
if (process.env.IMMICH_CONFIG_FILE) {
|
if (this.isUsingConfigFile()) {
|
||||||
throw new Error(`Invalid value(s) in file: ${errors}`);
|
throw new Error(`Invalid value(s) in file: ${errors}`);
|
||||||
} else {
|
} else {
|
||||||
this.logger.error('Validation error', errors);
|
this.logger.error('Validation error', errors);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger';
|
import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger';
|
||||||
import type { DateTime } from 'luxon';
|
import type { DateTime } from 'luxon';
|
||||||
import { FeatureFlags } from 'src/cores/system-config.core';
|
|
||||||
import { SystemConfigThemeDto } from 'src/dtos/system-config.dto';
|
import { SystemConfigThemeDto } from 'src/dtos/system-config.dto';
|
||||||
import { IVersion, VersionType } from 'src/utils/version';
|
import { IVersion, VersionType } from 'src/utils/version';
|
||||||
|
|
||||||
@@ -96,7 +95,7 @@ export class ServerConfigDto {
|
|||||||
externalDomain!: string;
|
externalDomain!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServerFeaturesDto implements FeatureFlags {
|
export class ServerFeaturesDto {
|
||||||
smartSearch!: boolean;
|
smartSearch!: boolean;
|
||||||
configFile!: boolean;
|
configFile!: boolean;
|
||||||
facialRecognition!: boolean;
|
facialRecognition!: boolean;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
ValidatorConstraint,
|
ValidatorConstraint,
|
||||||
ValidatorConstraintInterface,
|
ValidatorConstraintInterface,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { CLIPConfig, RecognitionConfig } from 'src/dtos/model-config.dto';
|
|
||||||
import {
|
import {
|
||||||
AudioCodec,
|
AudioCodec,
|
||||||
CQMode,
|
CQMode,
|
||||||
@@ -30,7 +29,8 @@ import {
|
|||||||
TranscodeHWAccel,
|
TranscodeHWAccel,
|
||||||
TranscodePolicy,
|
TranscodePolicy,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
} from 'src/entities/system-config.entity';
|
} from 'src/config';
|
||||||
|
import { CLIPConfig, RecognitionConfig } from 'src/dtos/model-config.dto';
|
||||||
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface';
|
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface';
|
||||||
import { ValidateBoolean, validateCronExpression } from 'src/validation';
|
import { ValidateBoolean, validateCronExpression } from 'src/validation';
|
||||||
|
|
||||||
@@ -132,6 +132,9 @@ export class SystemConfigFFmpegDto {
|
|||||||
@ApiProperty({ enumName: 'TranscodeHWAccel', enum: TranscodeHWAccel })
|
@ApiProperty({ enumName: 'TranscodeHWAccel', enum: TranscodeHWAccel })
|
||||||
accel!: TranscodeHWAccel;
|
accel!: TranscodeHWAccel;
|
||||||
|
|
||||||
|
@ValidateBoolean()
|
||||||
|
accelDecode!: boolean;
|
||||||
|
|
||||||
@IsEnum(ToneMapping)
|
@IsEnum(ToneMapping)
|
||||||
@ApiProperty({ enumName: 'ToneMapping', enum: ToneMapping })
|
@ApiProperty({ enumName: 'ToneMapping', enum: ToneMapping })
|
||||||
tonemap!: ToneMapping;
|
tonemap!: ToneMapping;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ConcurrentQueueName } from 'src/interfaces/job.interface';
|
import { SystemConfig } from 'src/config';
|
||||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
export type SystemConfigValue = string | string[] | number | boolean;
|
export type SystemConfigValue = string | string[] | number | boolean;
|
||||||
@@ -43,6 +43,7 @@ export const SystemConfigKey = {
|
|||||||
FFMPEG_PREFERRED_HW_DEVICE: 'ffmpeg.preferredHwDevice',
|
FFMPEG_PREFERRED_HW_DEVICE: 'ffmpeg.preferredHwDevice',
|
||||||
FFMPEG_TRANSCODE: 'ffmpeg.transcode',
|
FFMPEG_TRANSCODE: 'ffmpeg.transcode',
|
||||||
FFMPEG_ACCEL: 'ffmpeg.accel',
|
FFMPEG_ACCEL: 'ffmpeg.accel',
|
||||||
|
FFMPEG_ACCEL_DECODE: 'ffmpeg.accelDecode',
|
||||||
FFMPEG_TONEMAP: 'ffmpeg.tonemap',
|
FFMPEG_TONEMAP: 'ffmpeg.tonemap',
|
||||||
|
|
||||||
JOB_THUMBNAIL_GENERATION_CONCURRENCY: 'job.thumbnailGeneration.concurrency',
|
JOB_THUMBNAIL_GENERATION_CONCURRENCY: 'job.thumbnailGeneration.concurrency',
|
||||||
@@ -143,197 +144,3 @@ export class SystemConfigEntity<T = SystemConfigValue> {
|
|||||||
@Column({ type: 'varchar', nullable: true, transformer: { to: JSON.stringify, from: JSON.parse } })
|
@Column({ type: 'varchar', nullable: true, transformer: { to: JSON.stringify, from: JSON.parse } })
|
||||||
value!: T;
|
value!: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TranscodePolicy {
|
|
||||||
ALL = 'all',
|
|
||||||
OPTIMAL = 'optimal',
|
|
||||||
BITRATE = 'bitrate',
|
|
||||||
REQUIRED = 'required',
|
|
||||||
DISABLED = 'disabled',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum TranscodeTarget {
|
|
||||||
NONE,
|
|
||||||
AUDIO,
|
|
||||||
VIDEO,
|
|
||||||
ALL,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum VideoCodec {
|
|
||||||
H264 = 'h264',
|
|
||||||
HEVC = 'hevc',
|
|
||||||
VP9 = 'vp9',
|
|
||||||
AV1 = 'av1',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AudioCodec {
|
|
||||||
MP3 = 'mp3',
|
|
||||||
AAC = 'aac',
|
|
||||||
LIBOPUS = 'libopus',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum TranscodeHWAccel {
|
|
||||||
NVENC = 'nvenc',
|
|
||||||
QSV = 'qsv',
|
|
||||||
VAAPI = 'vaapi',
|
|
||||||
RKMPP = 'rkmpp',
|
|
||||||
DISABLED = 'disabled',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ToneMapping {
|
|
||||||
HABLE = 'hable',
|
|
||||||
MOBIUS = 'mobius',
|
|
||||||
REINHARD = 'reinhard',
|
|
||||||
DISABLED = 'disabled',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CQMode {
|
|
||||||
AUTO = 'auto',
|
|
||||||
CQP = 'cqp',
|
|
||||||
ICQ = 'icq',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Colorspace {
|
|
||||||
SRGB = 'srgb',
|
|
||||||
P3 = 'p3',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ImageFormat {
|
|
||||||
JPEG = 'jpeg',
|
|
||||||
WEBP = 'webp',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum LogLevel {
|
|
||||||
VERBOSE = 'verbose',
|
|
||||||
DEBUG = 'debug',
|
|
||||||
LOG = 'log',
|
|
||||||
WARN = 'warn',
|
|
||||||
ERROR = 'error',
|
|
||||||
FATAL = 'fatal',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SystemConfig {
|
|
||||||
ffmpeg: {
|
|
||||||
crf: number;
|
|
||||||
threads: number;
|
|
||||||
preset: string;
|
|
||||||
targetVideoCodec: VideoCodec;
|
|
||||||
acceptedVideoCodecs: VideoCodec[];
|
|
||||||
targetAudioCodec: AudioCodec;
|
|
||||||
acceptedAudioCodecs: AudioCodec[];
|
|
||||||
targetResolution: string;
|
|
||||||
maxBitrate: string;
|
|
||||||
bframes: number;
|
|
||||||
refs: number;
|
|
||||||
gopSize: number;
|
|
||||||
npl: number;
|
|
||||||
temporalAQ: boolean;
|
|
||||||
cqMode: CQMode;
|
|
||||||
twoPass: boolean;
|
|
||||||
preferredHwDevice: string;
|
|
||||||
transcode: TranscodePolicy;
|
|
||||||
accel: TranscodeHWAccel;
|
|
||||||
tonemap: ToneMapping;
|
|
||||||
};
|
|
||||||
job: Record<ConcurrentQueueName, { concurrency: number }>;
|
|
||||||
logging: {
|
|
||||||
enabled: boolean;
|
|
||||||
level: LogLevel;
|
|
||||||
};
|
|
||||||
machineLearning: {
|
|
||||||
enabled: boolean;
|
|
||||||
url: string;
|
|
||||||
clip: {
|
|
||||||
enabled: boolean;
|
|
||||||
modelName: string;
|
|
||||||
};
|
|
||||||
facialRecognition: {
|
|
||||||
enabled: boolean;
|
|
||||||
modelName: string;
|
|
||||||
minScore: number;
|
|
||||||
minFaces: number;
|
|
||||||
maxDistance: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
map: {
|
|
||||||
enabled: boolean;
|
|
||||||
lightStyle: string;
|
|
||||||
darkStyle: string;
|
|
||||||
};
|
|
||||||
reverseGeocoding: {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
oauth: {
|
|
||||||
autoLaunch: boolean;
|
|
||||||
autoRegister: boolean;
|
|
||||||
buttonText: string;
|
|
||||||
clientId: string;
|
|
||||||
clientSecret: string;
|
|
||||||
defaultStorageQuota: number;
|
|
||||||
enabled: boolean;
|
|
||||||
issuerUrl: string;
|
|
||||||
mobileOverrideEnabled: boolean;
|
|
||||||
mobileRedirectUri: string;
|
|
||||||
scope: string;
|
|
||||||
signingAlgorithm: string;
|
|
||||||
storageLabelClaim: string;
|
|
||||||
storageQuotaClaim: string;
|
|
||||||
};
|
|
||||||
passwordLogin: {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
storageTemplate: {
|
|
||||||
enabled: boolean;
|
|
||||||
hashVerificationEnabled: boolean;
|
|
||||||
template: string;
|
|
||||||
};
|
|
||||||
image: {
|
|
||||||
thumbnailFormat: ImageFormat;
|
|
||||||
thumbnailSize: number;
|
|
||||||
previewFormat: ImageFormat;
|
|
||||||
previewSize: number;
|
|
||||||
quality: number;
|
|
||||||
colorspace: Colorspace;
|
|
||||||
extractEmbedded: boolean;
|
|
||||||
};
|
|
||||||
newVersionCheck: {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
trash: {
|
|
||||||
enabled: boolean;
|
|
||||||
days: number;
|
|
||||||
};
|
|
||||||
theme: {
|
|
||||||
customCss: string;
|
|
||||||
};
|
|
||||||
library: {
|
|
||||||
scan: {
|
|
||||||
enabled: boolean;
|
|
||||||
cronExpression: string;
|
|
||||||
};
|
|
||||||
watch: {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
notifications: {
|
|
||||||
smtp: {
|
|
||||||
enabled: boolean;
|
|
||||||
from: string;
|
|
||||||
replyTo: string;
|
|
||||||
transport: {
|
|
||||||
ignoreCert: boolean;
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
server: {
|
|
||||||
externalDomain: string;
|
|
||||||
loginPageMessage: string;
|
|
||||||
};
|
|
||||||
user: {
|
|
||||||
deleteDelay: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { SystemConfig } from 'src/config';
|
||||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||||
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server-info.dto';
|
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server-info.dto';
|
||||||
import { SystemConfig } from 'src/entities/system-config.entity';
|
|
||||||
|
|
||||||
export const IEventRepository = 'IEventRepository';
|
export const IEventRepository = 'IEventRepository';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LogLevel } from 'src/entities/system-config.entity';
|
import { LogLevel } from 'src/config';
|
||||||
|
|
||||||
export const ILoggerRepository = 'ILoggerRepository';
|
export const ILoggerRepository = 'ILoggerRepository';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Writable } from 'node:stream';
|
import { Writable } from 'node:stream';
|
||||||
import { ImageFormat, TranscodeTarget, VideoCodec } from 'src/entities/system-config.entity';
|
import { ImageFormat, TranscodeTarget, VideoCodec } from 'src/config';
|
||||||
|
|
||||||
export const IMediaRepository = 'IMediaRepository';
|
export const IMediaRepository = 'IMediaRepository';
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,8 +7,8 @@ import { existsSync } from 'node:fs';
|
|||||||
import { Worker } from 'node:worker_threads';
|
import { Worker } from 'node:worker_threads';
|
||||||
import sirv from 'sirv';
|
import sirv from 'sirv';
|
||||||
import { ApiModule, ImmichAdminModule } from 'src/app.module';
|
import { ApiModule, ImmichAdminModule } from 'src/app.module';
|
||||||
|
import { LogLevel } from 'src/config';
|
||||||
import { WEB_ROOT, envName, excludePaths, isDev, serverVersion } from 'src/constants';
|
import { WEB_ROOT, envName, excludePaths, isDev, serverVersion } from 'src/constants';
|
||||||
import { LogLevel } from 'src/entities/system-config.entity';
|
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
||||||
import { ApiService } from 'src/services/api.service';
|
import { ApiService } from 'src/services/api.service';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ConsoleLogger, Injectable, Scope } from '@nestjs/common';
|
import { ConsoleLogger, Injectable, Scope } from '@nestjs/common';
|
||||||
import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util';
|
import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util';
|
||||||
import { ClsService } from 'nestjs-cls';
|
import { ClsService } from 'nestjs-cls';
|
||||||
import { LogLevel } from 'src/entities/system-config.entity';
|
import { LogLevel } from 'src/config';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { LogColor } from 'src/utils/logger-colors';
|
import { LogColor } from 'src/utils/logger-colors';
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import fs from 'node:fs/promises';
|
|||||||
import { Writable } from 'node:stream';
|
import { Writable } from 'node:stream';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { Colorspace } from 'src/entities/system-config.entity';
|
import { Colorspace } from 'src/config';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import {
|
import {
|
||||||
IMediaRepository,
|
IMediaRepository,
|
||||||
|
|||||||
@@ -434,12 +434,13 @@ export class AssetService {
|
|||||||
await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id: asset.livePhotoVideoId } });
|
await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id: asset.livePhotoVideoId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.jobRepository.queue({
|
const files = [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath];
|
||||||
name: JobName.DELETE_FILES,
|
// skip originals if the user deleted the whole library
|
||||||
data: {
|
if (!asset.library.deletedAt) {
|
||||||
files: [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath, asset.sidecarPath, asset.originalPath],
|
files.push(asset.sidecarPath, asset.originalPath);
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files } });
|
||||||
|
|
||||||
return JobStatus.SUCCESS;
|
return JobStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import cookieParser from 'cookie';
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { IncomingHttpHeaders } from 'node:http';
|
import { IncomingHttpHeaders } from 'node:http';
|
||||||
import { ClientMetadata, Issuer, UserinfoResponse, custom, generators } from 'openid-client';
|
import { ClientMetadata, Issuer, UserinfoResponse, custom, generators } from 'openid-client';
|
||||||
|
import { SystemConfig } from 'src/config';
|
||||||
import { AuthType, LOGIN_URL, MOBILE_REDIRECT } from 'src/constants';
|
import { AuthType, LOGIN_URL, MOBILE_REDIRECT } from 'src/constants';
|
||||||
import { AccessCore } from 'src/cores/access.core';
|
import { AccessCore } from 'src/cores/access.core';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||||
@@ -28,7 +29,6 @@ import {
|
|||||||
mapLoginResponse,
|
mapLoginResponse,
|
||||||
} from 'src/dtos/auth.dto';
|
} from 'src/dtos/auth.dto';
|
||||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||||
import { SystemConfig } from 'src/entities/system-config.entity';
|
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import { FeatureFlag, SystemConfigCore } from 'src/cores/system-config.core';
|
import { SystemConfig } from 'src/config';
|
||||||
import { SystemConfig, SystemConfigKey, SystemConfigKeyPaths } from 'src/entities/system-config.entity';
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import {
|
import {
|
||||||
@@ -367,32 +367,5 @@ describe(JobService.name, () => {
|
|||||||
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureTests: Array<{ queue: QueueName; feature: FeatureFlag; configKey: SystemConfigKeyPaths }> = [
|
|
||||||
{
|
|
||||||
queue: QueueName.SMART_SEARCH,
|
|
||||||
feature: FeatureFlag.SMART_SEARCH,
|
|
||||||
configKey: SystemConfigKey.MACHINE_LEARNING_CLIP_ENABLED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
queue: QueueName.FACE_DETECTION,
|
|
||||||
feature: FeatureFlag.FACIAL_RECOGNITION,
|
|
||||||
configKey: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_ENABLED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
queue: QueueName.FACIAL_RECOGNITION,
|
|
||||||
feature: FeatureFlag.FACIAL_RECOGNITION,
|
|
||||||
configKey: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_ENABLED,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const { queue, feature, configKey } of featureTests) {
|
|
||||||
it(`should throw an error if attempting to queue ${queue} when ${feature} is disabled`, async () => {
|
|
||||||
configMock.load.mockResolvedValue([{ key: configKey, value: false }]);
|
|
||||||
jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
|
||||||
|
|
||||||
await expect(sut.handleCommand(queue, { command: JobCommand.START, force: false })).rejects.toThrow();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { snakeCase } from 'lodash';
|
import { snakeCase } from 'lodash';
|
||||||
import { FeatureFlag, SystemConfigCore } from 'src/cores/system-config.core';
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||||
import { mapAsset } from 'src/dtos/asset-response.dto';
|
import { mapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { AllJobStatusResponseDto, JobCommandDto, JobStatusDto } from 'src/dtos/job.dto';
|
import { AllJobStatusResponseDto, JobCommandDto, JobStatusDto } from 'src/dtos/job.dto';
|
||||||
import { AssetType } from 'src/entities/asset.entity';
|
import { AssetType } from 'src/entities/asset.entity';
|
||||||
@@ -112,7 +112,6 @@ export class JobService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case QueueName.SMART_SEARCH: {
|
case QueueName.SMART_SEARCH: {
|
||||||
await this.configCore.requireFeature(FeatureFlag.SMART_SEARCH);
|
|
||||||
return this.jobRepository.queue({ name: JobName.QUEUE_SMART_SEARCH, data: { force } });
|
return this.jobRepository.queue({ name: JobName.QUEUE_SMART_SEARCH, data: { force } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +120,6 @@ export class JobService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case QueueName.SIDECAR: {
|
case QueueName.SIDECAR: {
|
||||||
await this.configCore.requireFeature(FeatureFlag.SIDECAR);
|
|
||||||
return this.jobRepository.queue({ name: JobName.QUEUE_SIDECAR, data: { force } });
|
return this.jobRepository.queue({ name: JobName.QUEUE_SIDECAR, data: { force } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,12 +128,10 @@ export class JobService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case QueueName.FACE_DETECTION: {
|
case QueueName.FACE_DETECTION: {
|
||||||
await this.configCore.requireFeature(FeatureFlag.FACIAL_RECOGNITION);
|
|
||||||
return this.jobRepository.queue({ name: JobName.QUEUE_FACE_DETECTION, data: { force } });
|
return this.jobRepository.queue({ name: JobName.QUEUE_FACE_DETECTION, data: { force } });
|
||||||
}
|
}
|
||||||
|
|
||||||
case QueueName.FACIAL_RECOGNITION: {
|
case QueueName.FACIAL_RECOGNITION: {
|
||||||
await this.configCore.requireFeature(FeatureFlag.FACIAL_RECOGNITION);
|
|
||||||
return this.jobRepository.queue({ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force } });
|
return this.jobRepository.queue({ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import { Stats } from 'node:fs';
|
import { Stats } from 'node:fs';
|
||||||
|
import { SystemConfig } from 'src/config';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||||
import { mapLibrary } from 'src/dtos/library.dto';
|
import { mapLibrary } from 'src/dtos/library.dto';
|
||||||
import { AssetType } from 'src/entities/asset.entity';
|
import { AssetType } from 'src/entities/asset.entity';
|
||||||
import { LibraryType } from 'src/entities/library.entity';
|
import { LibraryType } from 'src/entities/library.entity';
|
||||||
import { SystemConfig, SystemConfigKey } from 'src/entities/system-config.entity';
|
import { SystemConfigKey } from 'src/entities/system-config.entity';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Stats } from 'node:fs';
|
import { Stats } from 'node:fs';
|
||||||
import { AssetType } from 'src/entities/asset.entity';
|
|
||||||
import { ExifEntity } from 'src/entities/exif.entity';
|
|
||||||
import {
|
import {
|
||||||
AudioCodec,
|
AudioCodec,
|
||||||
Colorspace,
|
Colorspace,
|
||||||
ImageFormat,
|
ImageFormat,
|
||||||
SystemConfigKey,
|
|
||||||
ToneMapping,
|
ToneMapping,
|
||||||
TranscodeHWAccel,
|
TranscodeHWAccel,
|
||||||
TranscodePolicy,
|
TranscodePolicy,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
} from 'src/entities/system-config.entity';
|
} from 'src/config';
|
||||||
|
import { AssetType } from 'src/entities/asset.entity';
|
||||||
|
import { ExifEntity } from 'src/entities/exif.entity';
|
||||||
|
import { SystemConfigKey } from 'src/entities/system-config.entity';
|
||||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||||
@@ -1381,6 +1381,57 @@ describe(MediaService.name, () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should use hardware decoding for nvenc if enabled', async () => {
|
||||||
|
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||||
|
configMock.load.mockResolvedValue([
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
|
||||||
|
]);
|
||||||
|
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
||||||
|
await sut.handleVideoConversion({ id: assetStub.video.id });
|
||||||
|
expect(mediaMock.transcode).toHaveBeenCalledWith(
|
||||||
|
'/original/path.ext',
|
||||||
|
'upload/encoded-video/user-id/as/se/asset-id.mp4',
|
||||||
|
{
|
||||||
|
inputOptions: expect.arrayContaining([
|
||||||
|
'-hwaccel cuda',
|
||||||
|
'-hwaccel_output_format cuda',
|
||||||
|
'-noautorotate',
|
||||||
|
'-threads 1',
|
||||||
|
]),
|
||||||
|
outputOptions: expect.arrayContaining([
|
||||||
|
expect.stringContaining(
|
||||||
|
'scale_cuda=-2:720,hwupload=derive_device=vulkan,libplacebo=color_primaries=bt709:color_trc=bt709:colorspace=bt709:deband=true:deband_iterations=3:deband_radius=8:deband_threshold=6:downscaler=none:format=yuv420p:tonemapping=clip:upscaler=none,hwupload=derive_device=cuda',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
twoPass: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use hardware tone-mapping for nvenc if hardware decoding is enabled and should tone map', async () => {
|
||||||
|
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
|
||||||
|
configMock.load.mockResolvedValue([
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
|
||||||
|
]);
|
||||||
|
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
||||||
|
await sut.handleVideoConversion({ id: assetStub.video.id });
|
||||||
|
expect(mediaMock.transcode).toHaveBeenCalledWith(
|
||||||
|
'/original/path.ext',
|
||||||
|
'upload/encoded-video/user-id/as/se/asset-id.mp4',
|
||||||
|
{
|
||||||
|
inputOptions: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']),
|
||||||
|
outputOptions: expect.arrayContaining([
|
||||||
|
expect.stringContaining(
|
||||||
|
'hwupload=derive_device=vulkan,libplacebo=color_primaries=bt709:color_trc=bt709:colorspace=bt709:deband=true:deband_iterations=3:deband_radius=8:deband_threshold=6:downscaler=none:format=yuv420p:tonemapping=hable:upscaler=none,hwupload=derive_device=cuda',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
twoPass: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should set options for qsv', async () => {
|
it('should set options for qsv', async () => {
|
||||||
storageMock.readdir.mockResolvedValue(['renderD128']);
|
storageMock.readdir.mockResolvedValue(['renderD128']);
|
||||||
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||||
@@ -1693,8 +1744,12 @@ describe(MediaService.name, () => {
|
|||||||
|
|
||||||
it('should set options for rkmpp', async () => {
|
it('should set options for rkmpp', async () => {
|
||||||
storageMock.readdir.mockResolvedValue(['renderD128']);
|
storageMock.readdir.mockResolvedValue(['renderD128']);
|
||||||
|
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
|
||||||
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||||
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP }]);
|
configMock.load.mockResolvedValue([
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
|
||||||
|
]);
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
||||||
await sut.handleVideoConversion({ id: assetStub.video.id });
|
await sut.handleVideoConversion({ id: assetStub.video.id });
|
||||||
expect(mediaMock.transcode).toHaveBeenCalledWith(
|
expect(mediaMock.transcode).toHaveBeenCalledWith(
|
||||||
@@ -1723,9 +1778,11 @@ describe(MediaService.name, () => {
|
|||||||
|
|
||||||
it('should set vbr options for rkmpp when max bitrate is enabled', async () => {
|
it('should set vbr options for rkmpp when max bitrate is enabled', async () => {
|
||||||
storageMock.readdir.mockResolvedValue(['renderD128']);
|
storageMock.readdir.mockResolvedValue(['renderD128']);
|
||||||
|
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
|
||||||
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVp9);
|
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVp9);
|
||||||
configMock.load.mockResolvedValue([
|
configMock.load.mockResolvedValue([
|
||||||
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
|
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
|
||||||
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '10000k' },
|
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '10000k' },
|
||||||
{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC },
|
{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC },
|
||||||
]);
|
]);
|
||||||
@@ -1744,9 +1801,11 @@ describe(MediaService.name, () => {
|
|||||||
|
|
||||||
it('should set cqp options for rkmpp when max bitrate is disabled', async () => {
|
it('should set cqp options for rkmpp when max bitrate is disabled', async () => {
|
||||||
storageMock.readdir.mockResolvedValue(['renderD128']);
|
storageMock.readdir.mockResolvedValue(['renderD128']);
|
||||||
|
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
|
||||||
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||||
configMock.load.mockResolvedValue([
|
configMock.load.mockResolvedValue([
|
||||||
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
|
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
|
||||||
{ key: SystemConfigKey.FFMPEG_CRF, value: 30 },
|
{ key: SystemConfigKey.FFMPEG_CRF, value: 30 },
|
||||||
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '0' },
|
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '0' },
|
||||||
]);
|
]);
|
||||||
@@ -1769,6 +1828,7 @@ describe(MediaService.name, () => {
|
|||||||
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
|
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
|
||||||
configMock.load.mockResolvedValue([
|
configMock.load.mockResolvedValue([
|
||||||
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
|
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
|
||||||
{ key: SystemConfigKey.FFMPEG_CRF, value: 30 },
|
{ key: SystemConfigKey.FFMPEG_CRF, value: 30 },
|
||||||
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '0' },
|
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '0' },
|
||||||
]);
|
]);
|
||||||
@@ -1788,6 +1848,60 @@ describe(MediaService.name, () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should use software decoding and tone-mapping if hardware decoding is disabled', async () => {
|
||||||
|
storageMock.readdir.mockResolvedValue(['renderD128']);
|
||||||
|
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => true, isCharacterDevice: () => true });
|
||||||
|
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
|
||||||
|
configMock.load.mockResolvedValue([
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: false },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_CRF, value: 30 },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '0' },
|
||||||
|
]);
|
||||||
|
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
||||||
|
await sut.handleVideoConversion({ id: assetStub.video.id });
|
||||||
|
expect(mediaMock.transcode).toHaveBeenCalledWith(
|
||||||
|
'/original/path.ext',
|
||||||
|
'upload/encoded-video/user-id/as/se/asset-id.mp4',
|
||||||
|
{
|
||||||
|
inputOptions: [],
|
||||||
|
outputOptions: expect.arrayContaining([
|
||||||
|
expect.stringContaining(
|
||||||
|
'zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:range=pc,format=yuv420p',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
twoPass: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use software decoding and tone-mapping if opencl is not available', async () => {
|
||||||
|
storageMock.readdir.mockResolvedValue(['renderD128']);
|
||||||
|
storageMock.stat.mockResolvedValue({ ...new Stats(), isFile: () => false, isCharacterDevice: () => false });
|
||||||
|
mediaMock.probe.mockResolvedValue(probeStub.videoStreamHDR);
|
||||||
|
configMock.load.mockResolvedValue([
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_ACCEL_DECODE, value: true },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_CRF, value: 30 },
|
||||||
|
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '0' },
|
||||||
|
]);
|
||||||
|
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
||||||
|
await sut.handleVideoConversion({ id: assetStub.video.id });
|
||||||
|
expect(mediaMock.transcode).toHaveBeenCalledWith(
|
||||||
|
'/original/path.ext',
|
||||||
|
'upload/encoded-video/user-id/as/se/asset-id.mp4',
|
||||||
|
{
|
||||||
|
inputOptions: [],
|
||||||
|
outputOptions: expect.arrayContaining([
|
||||||
|
expect.stringContaining(
|
||||||
|
'zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:range=pc,format=yuv420p',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
twoPass: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should tonemap when policy is required and video is hdr', async () => {
|
it('should tonemap when policy is required and video is hdr', async () => {
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common';
|
import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common';
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import { GeneratedImageType, StorageCore, StorageFolder } from 'src/cores/storage.core';
|
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
|
|
||||||
import { AssetEntity, AssetType } from 'src/entities/asset.entity';
|
|
||||||
import { AssetPathType } from 'src/entities/move.entity';
|
|
||||||
import {
|
import {
|
||||||
AudioCodec,
|
AudioCodec,
|
||||||
Colorspace,
|
Colorspace,
|
||||||
@@ -13,7 +8,12 @@ import {
|
|||||||
TranscodePolicy,
|
TranscodePolicy,
|
||||||
TranscodeTarget,
|
TranscodeTarget,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
} from 'src/entities/system-config.entity';
|
} from 'src/config';
|
||||||
|
import { GeneratedImageType, StorageCore, StorageFolder } from 'src/cores/storage.core';
|
||||||
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||||
|
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
|
||||||
|
import { AssetEntity, AssetType } from 'src/entities/asset.entity';
|
||||||
|
import { AssetPathType } from 'src/entities/move.entity';
|
||||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import {
|
import {
|
||||||
@@ -36,9 +36,11 @@ import {
|
|||||||
AV1Config,
|
AV1Config,
|
||||||
H264Config,
|
H264Config,
|
||||||
HEVCConfig,
|
HEVCConfig,
|
||||||
NVENCConfig,
|
NvencHwDecodeConfig,
|
||||||
|
NvencSwDecodeConfig,
|
||||||
QSVConfig,
|
QSVConfig,
|
||||||
RKMPPConfig,
|
RkmppHwDecodeConfig,
|
||||||
|
RkmppSwDecodeConfig,
|
||||||
ThumbnailConfig,
|
ThumbnailConfig,
|
||||||
VAAPIConfig,
|
VAAPIConfig,
|
||||||
VP9Config,
|
VP9Config,
|
||||||
@@ -361,8 +363,7 @@ export class MediaService {
|
|||||||
`Error occurred during transcoding. Retrying with ${config.accel.toUpperCase()} acceleration disabled.`,
|
`Error occurred during transcoding. Retrying with ${config.accel.toUpperCase()} acceleration disabled.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
config.accel = TranscodeHWAccel.DISABLED;
|
transcodeOptions = await this.getCodecConfig({ ...config, accel: TranscodeHWAccel.DISABLED }).then((c) =>
|
||||||
transcodeOptions = await this.getCodecConfig(config).then((c) =>
|
|
||||||
c.getOptions(target, mainVideoStream, mainAudioStream),
|
c.getOptions(target, mainVideoStream, mainAudioStream),
|
||||||
);
|
);
|
||||||
await this.mediaRepository.transcode(input, output, transcodeOptions);
|
await this.mediaRepository.transcode(input, output, transcodeOptions);
|
||||||
@@ -495,7 +496,7 @@ export class MediaService {
|
|||||||
let handler: VideoCodecHWConfig;
|
let handler: VideoCodecHWConfig;
|
||||||
switch (config.accel) {
|
switch (config.accel) {
|
||||||
case TranscodeHWAccel.NVENC: {
|
case TranscodeHWAccel.NVENC: {
|
||||||
handler = new NVENCConfig(config);
|
handler = config.accelDecode ? new NvencHwDecodeConfig(config) : new NvencSwDecodeConfig(config);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TranscodeHWAccel.QSV: {
|
case TranscodeHWAccel.QSV: {
|
||||||
@@ -507,7 +508,10 @@ export class MediaService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TranscodeHWAccel.RKMPP: {
|
case TranscodeHWAccel.RKMPP: {
|
||||||
handler = new RKMPPConfig(config, await this.getDevices(), await this.hasOpenCL());
|
handler =
|
||||||
|
config.accelDecode && (await this.hasOpenCL())
|
||||||
|
? new RkmppHwDecodeConfig(config, await this.getDevices())
|
||||||
|
: new RkmppSwDecodeConfig(config, await this.getDevices());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user