mirror of
https://github.com/immich-app/immich.git
synced 2026-07-07 04:57:05 -07:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e8bbbc56cb | |||
| 6f45521610 | |||
| 69d568f289 | |||
| a6f0253544 | |||
| 32352fa375 | |||
| 6aa5651032 | |||
| 4e25db2989 | |||
| 4f5e0d3505 | |||
| b3abe0bb5d | |||
| c04acc8951 | |||
| af32a8ca7c | |||
| ed83fb3f5a | |||
| 628de417e9 | |||
| be02c00afd | |||
| 43abb9a92f | |||
| 103585ceb9 | |||
| 6da890ee37 | |||
| e7ba8d3632 | |||
| 5645149e09 | |||
| 5a104f9dbe | |||
| 13d17b3a11 | |||
| f77c8a4699 | |||
| bf4b020856 | |||
| f73796597c | |||
| 237734bb26 | |||
| 4b54fef82e | |||
| 0050332391 |
@@ -103,14 +103,14 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
|
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
|
||||||
|
|
||||||
- uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Restore Gradle Cache
|
- name: Restore Gradle Cache
|
||||||
id: cache-gradle-restore
|
id: cache-gradle-restore
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save Gradle Cache
|
- name: Save Gradle Cache
|
||||||
id: cache-gradle-save
|
id: cache-gradle-save
|
||||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -237,7 +237,7 @@ jobs:
|
|||||||
run: flutter build ios --config-only --no-codesign
|
run: flutter build ios --config-only --no-codesign
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0
|
uses: ruby/setup-ruby@0dafeac902942906541bc140009cdbf32665b601 # v1.315.0
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.3'
|
ruby-version: '3.3'
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Check for breaking API changes
|
- name: Check for breaking API changes
|
||||||
uses: oasdiff/oasdiff-action/breaking@e24529087d93f837b28b50bb66ba9016380a7fcc # v0.1.2
|
uses: oasdiff/oasdiff-action/breaking@ae5ef6402ebe218ac6e872c59d0fa4557694e75c # v0.1.4
|
||||||
with:
|
with:
|
||||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||||
revision: open-api/immich-openapi-specs.json
|
revision: open-api/immich-openapi-specs.json
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: ${{ needs.bump_version.outputs.rc }}
|
prerelease: ${{ needs.bump_version.outputs.rc }}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
<a href="readme_i18n/README_ar_JO.md">العربية</a>
|
<a href="readme_i18n/README_ar_JO.md">العربية</a>
|
||||||
<a href="readme_i18n/README_vi_VN.md">Tiếng Việt</a>
|
<a href="readme_i18n/README_vi_VN.md">Tiếng Việt</a>
|
||||||
<a href="readme_i18n/README_th_TH.md">ภาษาไทย</a>
|
<a href="readme_i18n/README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="readme_i18n/README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,30 +7,37 @@ backend = "aqua:opentofu/opentofu"
|
|||||||
[tools.opentofu."platforms.linux-arm64"]
|
[tools.opentofu."platforms.linux-arm64"]
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64-musl"]
|
[tools.opentofu."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64"]
|
[tools.opentofu."platforms.linux-x64"]
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64-musl"]
|
[tools.opentofu."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-arm64"]
|
[tools.opentofu."platforms.macos-arm64"]
|
||||||
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536399"
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-x64"]
|
[tools.opentofu."platforms.macos-x64"]
|
||||||
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536384"
|
||||||
|
|
||||||
[tools.opentofu."platforms.windows-x64"]
|
[tools.opentofu."platforms.windows-x64"]
|
||||||
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536406"
|
||||||
|
|
||||||
[[tools.terragrunt]]
|
[[tools.terragrunt]]
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -39,27 +46,34 @@ backend = "aqua:gruntwork-io/terragrunt"
|
|||||||
[tools.terragrunt."platforms.linux-arm64"]
|
[tools.terragrunt."platforms.linux-arm64"]
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-arm64-musl"]
|
[tools.terragrunt."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-x64"]
|
[tools.terragrunt."platforms.linux-x64"]
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-x64-musl"]
|
[tools.terragrunt."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.macos-arm64"]
|
[tools.terragrunt."platforms.macos-arm64"]
|
||||||
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654084"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.macos-x64"]
|
[tools.terragrunt."platforms.macos-x64"]
|
||||||
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406653970"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.windows-x64"]
|
[tools.terragrunt."platforms.windows-x64"]
|
||||||
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654412"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ You can trigger a database backup manually:
|
|||||||
|
|
||||||
1. Go to **Administration > Job Queues**
|
1. Go to **Administration > Job Queues**
|
||||||
2. Click **Create job** in the top right
|
2. Click **Create job** in the top right
|
||||||
3. Select **Create Database Backup** and click **Confirm**
|
3. Select **Create Database Dump** and click **Confirm**
|
||||||
|
|
||||||
The backup will appear in `UPLOAD_LOCATION/backups` and counts toward your retention limit.
|
The backup will appear in `UPLOAD_LOCATION/backups` and counts toward your retention limit.
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,15 @@ docker image prune
|
|||||||
|
|
||||||
## Versioning Policy
|
## Versioning Policy
|
||||||
|
|
||||||
Immich follows [semantic versioning][semver], which tags releases in the format `<major>.<minor>.<patch>`. We intend for breaking changes to be limited to major version releases.
|
Immich follows [semantic versioning][semver], which tags releases in the format `<major>.<minor>.<patch>`.
|
||||||
You can configure your Docker image to point to the current major version by using a metatag, such as `:v3`.
|
We intend for breaking changes, including those to the API or deployment, to be limited to major version releases.
|
||||||
|
You can configure your Docker image to point to the current major version by using a metatag, such as `:v3`. These metatags do not follow release candidates.
|
||||||
|
|
||||||
Currently, we have no plans to backport patches to earlier versions. We encourage all users to run the most recent release of Immich.
|
The mobile app is typically compatible with the current and prior major version. However, the server is only compatible with the matching major version.
|
||||||
Switching back to an earlier version, even within the same minor release tag, is not supported.
|
Thus, we recommend upgrading all mobile clients before upgrading the server to ensure compatibility.
|
||||||
|
|
||||||
|
We do not backport patches to earlier versions. We encourage all users to run the most recent stable release of Immich.
|
||||||
|
Downgrading to an earlier version, even within the same minor version, is not supported.
|
||||||
|
|
||||||
[semver]: https://semver.org/
|
[semver]: https://semver.org/
|
||||||
|
|
||||||
|
|||||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"label": "v3.0.0",
|
"label": "v3.0.1",
|
||||||
"url": "https://docs.v3.0.0.archive.immich.app"
|
"url": "https://docs.v3.0.1.archive.immich.app"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "v2.7.5",
|
"label": "v2.7.5",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -91,12 +91,14 @@ describe('/server', () => {
|
|||||||
it('should respond with the server version', async () => {
|
it('should respond with the server version', async () => {
|
||||||
const { status, body } = await request(app).get('/server/version');
|
const { status, body } = await request(app).get('/server/version');
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
major: expect.any(Number),
|
major: expect.any(Number),
|
||||||
minor: expect.any(Number),
|
minor: expect.any(Number),
|
||||||
patch: expect.any(Number),
|
patch: expect.any(Number),
|
||||||
prerelease: expect.anything(),
|
}),
|
||||||
});
|
);
|
||||||
|
expect(Object.keys(body)).toEqual(expect.arrayContaining(['major', 'minor', 'patch', 'prerelease']));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
|
|||||||
cast: {
|
cast: {
|
||||||
gCastEnabled: false,
|
gCastEnabled: false,
|
||||||
},
|
},
|
||||||
|
recentlyAdded: {
|
||||||
|
sidebarWeb: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ export async function enableTagsPreference(context: BrowserContext) {
|
|||||||
download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false },
|
download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false },
|
||||||
purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' },
|
purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' },
|
||||||
cast: { gCastEnabled: false },
|
cast: { gCastEnabled: false },
|
||||||
|
recentlyAdded: { sidebarWeb: false },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -431,6 +431,10 @@
|
|||||||
"transcoding_realtime_description": "Allows transcoding to be performed in real-time as the video is being streamed. Enables quality switching, but may cause higher playback latency and stuttering depending on server capabilities.",
|
"transcoding_realtime_description": "Allows transcoding to be performed in real-time as the video is being streamed. Enables quality switching, but may cause higher playback latency and stuttering depending on server capabilities.",
|
||||||
"transcoding_realtime_enabled": "Enable real-time transcoding",
|
"transcoding_realtime_enabled": "Enable real-time transcoding",
|
||||||
"transcoding_realtime_enabled_description": "If disabled, the server will refuse to start new real-time transcoding sessions.",
|
"transcoding_realtime_enabled_description": "If disabled, the server will refuse to start new real-time transcoding sessions.",
|
||||||
|
"transcoding_realtime_resolutions": "Resolutions",
|
||||||
|
"transcoding_realtime_resolutions_description": "The resolutions offered for real-time transcoding. A variant is only offered when its resolution is no larger than the source. Higher resolutions may cause playback issues if the server cannot transcode them quickly enough.",
|
||||||
|
"transcoding_realtime_video_codecs": "Video codecs",
|
||||||
|
"transcoding_realtime_video_codecs_description": "The video codecs offered for real-time transcoding. Clients will choose the best option they support during playback. AV1 is more efficient than HEVC, which is more efficient than H.264. When using hardware acceleration, only select the codecs the accelerator can encode. When using software transcoding, note that H.264 is faster than AV1, which is faster than HEVC.",
|
||||||
"transcoding_reference_frames": "Reference frames",
|
"transcoding_reference_frames": "Reference frames",
|
||||||
"transcoding_reference_frames_description": "The number of frames to reference when compressing a given frame. Higher values improve compression efficiency, but slow down encoding. 0 sets this value automatically.",
|
"transcoding_reference_frames_description": "The number of frames to reference when compressing a given frame. Higher values improve compression efficiency, but slow down encoding. 0 sets this value automatically.",
|
||||||
"transcoding_required_description": "Only videos not in an accepted format",
|
"transcoding_required_description": "Only videos not in an accepted format",
|
||||||
@@ -593,6 +597,7 @@
|
|||||||
"asset_viewer_settings_title": "Asset Viewer",
|
"asset_viewer_settings_title": "Asset Viewer",
|
||||||
"assets": "Assets",
|
"assets": "Assets",
|
||||||
"assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album",
|
"assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album",
|
||||||
|
"assets_added_to_album_partial_count": "Added {successCount} out of {totalCount} {totalCount, plural, one {asset} other {assets}} to the album",
|
||||||
"assets_added_to_albums_count": "Added {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}",
|
"assets_added_to_albums_count": "Added {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}",
|
||||||
"assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album",
|
"assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album",
|
||||||
"assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} cannot be added to any of the albums",
|
"assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} cannot be added to any of the albums",
|
||||||
@@ -1718,6 +1723,7 @@
|
|||||||
"recent_searches": "Recent searches",
|
"recent_searches": "Recent searches",
|
||||||
"recently_added": "Recently added",
|
"recently_added": "Recently added",
|
||||||
"recently_added_body": "Jump straight to everything you've added lately on a dedicated page.",
|
"recently_added_body": "Jump straight to everything you've added lately on a dedicated page.",
|
||||||
|
"recently_added_description": "Browse your assets sorted by when they were uploaded to Immich",
|
||||||
"recently_added_page_title": "Recently Added",
|
"recently_added_page_title": "Recently Added",
|
||||||
"recently_added_title": "Recently added",
|
"recently_added_title": "Recently added",
|
||||||
"recently_taken": "Recently taken",
|
"recently_taken": "Recently taken",
|
||||||
@@ -2149,6 +2155,7 @@
|
|||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"unknown_country": "Unknown Country",
|
"unknown_country": "Unknown Country",
|
||||||
"unknown_date": "Unknown date",
|
"unknown_date": "Unknown date",
|
||||||
|
"unknown_schema": "Unknown schema",
|
||||||
"unknown_year": "Unknown Year",
|
"unknown_year": "Unknown Year",
|
||||||
"unlimited": "Unlimited",
|
"unlimited": "Unlimited",
|
||||||
"unlink_motion_video": "Unlink motion video",
|
"unlink_motion_video": "Unlink motion video",
|
||||||
|
|||||||
+29
-1
@@ -6,7 +6,7 @@
|
|||||||
"action": "Aksion",
|
"action": "Aksion",
|
||||||
"action_common_update": "Përditëso",
|
"action_common_update": "Përditëso",
|
||||||
"action_description": "Një grup veprimesh për t'u kryer në asetet e filtruara",
|
"action_description": "Një grup veprimesh për t'u kryer në asetet e filtruara",
|
||||||
"actions": "Aksione",
|
"actions": "Veprime",
|
||||||
"active": "Aktiv",
|
"active": "Aktiv",
|
||||||
"active_count": "Aktive: {count}",
|
"active_count": "Aktive: {count}",
|
||||||
"activity": "Aktivitet",
|
"activity": "Aktivitet",
|
||||||
@@ -536,6 +536,11 @@
|
|||||||
"api_keys": "Çelësat API",
|
"api_keys": "Çelësat API",
|
||||||
"app_architecture_variant": "Varianta (Arkitektura)",
|
"app_architecture_variant": "Varianta (Arkitektura)",
|
||||||
"app_bar_signout_dialog_content": "A je i sigurt që dëshiron të dalësh?",
|
"app_bar_signout_dialog_content": "A je i sigurt që dëshiron të dalësh?",
|
||||||
|
"back": "Mbrapa",
|
||||||
|
"close": "Mbyll",
|
||||||
|
"copy_image": "Kopjo imazhin",
|
||||||
|
"dark": "E errët",
|
||||||
|
"disabled": "I çaktivizuar",
|
||||||
"download_original": "Shkarko origjinalin",
|
"download_original": "Shkarko origjinalin",
|
||||||
"download_paused": "Shkarkimi u pezullua",
|
"download_paused": "Shkarkimi u pezullua",
|
||||||
"download_settings": "Shkarko",
|
"download_settings": "Shkarko",
|
||||||
@@ -544,6 +549,29 @@
|
|||||||
"downloading_asset_filename": "Duke shkarkuar asetin {filename}",
|
"downloading_asset_filename": "Duke shkarkuar asetin {filename}",
|
||||||
"downloading_from_icloud": "Duke shkarkuar nga iCloud",
|
"downloading_from_icloud": "Duke shkarkuar nga iCloud",
|
||||||
"downloading_media": "Duke shkarkuar median",
|
"downloading_media": "Duke shkarkuar median",
|
||||||
|
"enable": "Aktivizo",
|
||||||
|
"error": "Gabim",
|
||||||
|
"expired": "Skaduar",
|
||||||
|
"image": "Imazhi",
|
||||||
|
"info": "Info",
|
||||||
|
"model": "Modeli",
|
||||||
|
"name": "Emri",
|
||||||
|
"none": "Asnjë",
|
||||||
|
"offline": "Jashtë linje",
|
||||||
|
"ok": "Në rregull",
|
||||||
|
"online": "Online",
|
||||||
|
"path": "Shtegu",
|
||||||
|
"refresh": "Rifresko",
|
||||||
|
"rename": "Riemërto",
|
||||||
|
"search": "Kërko",
|
||||||
|
"settings": "Cilësimet",
|
||||||
|
"size": "Madhësia",
|
||||||
|
"status": "Statusi",
|
||||||
|
"type": "Lloji",
|
||||||
|
"unknown": "E panjohur",
|
||||||
|
"upload_finished": "Ngarkimi përfundoi",
|
||||||
|
"username": "Emri i përdoruesit",
|
||||||
|
"version": "Versioni",
|
||||||
"you_dont_have_any_shared_links": "Nuk keni asnjë link të shpërndarë",
|
"you_dont_have_any_shared_links": "Nuk keni asnjë link të shpërndarë",
|
||||||
"your_wifi_name": "Emri i Wi-Fi tuaj",
|
"your_wifi_name": "Emri i Wi-Fi tuaj",
|
||||||
"zoom_image": "Zmadho imazhin"
|
"zoom_image": "Zmadho imazhin"
|
||||||
|
|||||||
+21
-14
@@ -5,38 +5,38 @@ version = "3.11.15"
|
|||||||
backend = "core:python"
|
backend = "core:python"
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64"]
|
[tools.python."platforms.linux-arm64"]
|
||||||
checksum = "sha256:cbce0660e88cd9c56be7aaf2a2df92bea51f359388a521838b6b01817d728df0"
|
checksum = "sha256:9ac18c9a761e91e6c6452bc0ef0082922a00a3fdec734555635d57c3169309b7"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64-musl"]
|
[tools.python."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:a55ea44225ee3741d4157c383f3d5c3e8eee5f9665e2ea069233486b4275d928"
|
checksum = "sha256:bcd414f2e9d8cc5d4ac7fab2c8f912e30379c150fa6b94da19d64daf85cfc5d4"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-unknown-linux-musl-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-aarch64-unknown-linux-musl-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64"]
|
[tools.python."platforms.linux-x64"]
|
||||||
checksum = "sha256:67a5b22f796e96f4d7fa628f95866d5fd1d524d0588f74e4601facd82b66792b"
|
checksum = "sha256:0604cd029b142dc223e131f17f5941c0c8d2d5074997c8178b515b19eea2a6c2"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64-musl"]
|
[tools.python."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:5a8544aa4303da3ca4b7505c98dd8453b671157039d25cd551e55abea0f83a60"
|
checksum = "sha256:45bffdb320cf4a2c2dbf183b634aeb7e28f9cb0646f715b2f4117c11084d839a"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-unknown-linux-musl-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-unknown-linux-musl-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.macos-arm64"]
|
[tools.python."platforms.macos-arm64"]
|
||||||
checksum = "sha256:8c56f1f59142e0f9f8861ad897bdfd97fd84403afa7b3d8b0f33b208ec471355"
|
checksum = "sha256:2318799eaf104f8a29bc09a93b0851b05dbbcb4ce9a5f045ddea169c0c7ff3a5"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.macos-x64"]
|
[tools.python."platforms.macos-x64"]
|
||||||
checksum = "sha256:8cd3878c656ba1698314cbcb65f78df4c37b7c8eabff958558115c6db11adb3d"
|
checksum = "sha256:4925e5aaa9bc77c85302d350b36c1d9def2002996a6bcfa55c88ba6eb318de29"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.windows-x64"]
|
[tools.python."platforms.windows-x64"]
|
||||||
checksum = "sha256:f081a733b4e7ba0e5e5e12d533b3c795dbef3ecbebf92f0b4202e5329bf7c8ab"
|
checksum = "sha256:6589ca6d63f520bec4096d62b3ab91da3d0a80b16b594c99a6b677e335814683"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[[tools.uv]]
|
[[tools.uv]]
|
||||||
@@ -46,27 +46,34 @@ backend = "aqua:astral-sh/uv"
|
|||||||
[tools.uv."platforms.linux-arm64"]
|
[tools.uv."platforms.linux-arm64"]
|
||||||
checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2"
|
checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2"
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz"
|
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235419"
|
||||||
|
|
||||||
[tools.uv."platforms.linux-arm64-musl"]
|
[tools.uv."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2"
|
checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2"
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz"
|
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235419"
|
||||||
|
|
||||||
[tools.uv."platforms.linux-x64"]
|
[tools.uv."platforms.linux-x64"]
|
||||||
checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea"
|
checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea"
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz"
|
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235475"
|
||||||
|
|
||||||
[tools.uv."platforms.linux-x64-musl"]
|
[tools.uv."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea"
|
checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea"
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz"
|
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235475"
|
||||||
|
|
||||||
[tools.uv."platforms.macos-arm64"]
|
[tools.uv."platforms.macos-arm64"]
|
||||||
checksum = "sha256:103367962c5cb00bf7370d84cbaa3fec5a9807be9cc833ea9d8eea400c119fa2"
|
checksum = "sha256:103367962c5cb00bf7370d84cbaa3fec5a9807be9cc833ea9d8eea400c119fa2"
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-apple-darwin.tar.gz"
|
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-apple-darwin.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235412"
|
||||||
|
|
||||||
[tools.uv."platforms.macos-x64"]
|
[tools.uv."platforms.macos-x64"]
|
||||||
checksum = "sha256:2bbef70982e97dfc36454de173f35ec1a5e83ae11e3885df6a50db3fd76171cb"
|
checksum = "sha256:2bbef70982e97dfc36454de173f35ec1a5e83ae11e3885df6a50db3fd76171cb"
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-apple-darwin.tar.gz"
|
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-apple-darwin.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235464"
|
||||||
|
|
||||||
[tools.uv."platforms.windows-x64"]
|
[tools.uv."platforms.windows-x64"]
|
||||||
checksum = "sha256:459d95892a5cc5c21779532f4f41b9238594b79e312a5142da2148ecfa10e705"
|
checksum = "sha256:459d95892a5cc5c21779532f4f41b9238594b79e312a5142da2148ecfa10e705"
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-pc-windows-msvc.zip"
|
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-pc-windows-msvc.zip"
|
||||||
|
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235467"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "immich-ml"
|
name = "immich-ml"
|
||||||
version = "3.0.0"
|
version = "3.0.1"
|
||||||
description = ""
|
description = ""
|
||||||
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
|
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
|
||||||
requires-python = ">=3.11,<4.0"
|
requires-python = ">=3.11,<4.0"
|
||||||
|
|||||||
Generated
+1
-1
@@ -974,7 +974,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "immich-ml"
|
name = "immich-ml"
|
||||||
version = "3.0.0"
|
version = "3.0.1"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiocache" },
|
{ name = "aiocache" },
|
||||||
|
|||||||
@@ -254,30 +254,37 @@ backend = "aqua:opentofu/opentofu"
|
|||||||
[tools.opentofu."platforms.linux-arm64"]
|
[tools.opentofu."platforms.linux-arm64"]
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64-musl"]
|
[tools.opentofu."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64"]
|
[tools.opentofu."platforms.linux-x64"]
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64-musl"]
|
[tools.opentofu."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-arm64"]
|
[tools.opentofu."platforms.macos-arm64"]
|
||||||
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536399"
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-x64"]
|
[tools.opentofu."platforms.macos-x64"]
|
||||||
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536384"
|
||||||
|
|
||||||
[tools.opentofu."platforms.windows-x64"]
|
[tools.opentofu."platforms.windows-x64"]
|
||||||
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536406"
|
||||||
|
|
||||||
[[tools.pnpm]]
|
[[tools.pnpm]]
|
||||||
version = "11.6.0"
|
version = "11.6.0"
|
||||||
@@ -286,31 +293,37 @@ backend = "aqua:pnpm/pnpm"
|
|||||||
[tools.pnpm."platforms.linux-arm64"]
|
[tools.pnpm."platforms.linux-arm64"]
|
||||||
checksum = "sha256:2fec653ff6dadab340d1c3d2214688a7451cc471f39710839440b293ca7c53b0"
|
checksum = "sha256:2fec653ff6dadab340d1c3d2214688a7451cc471f39710839440b293ca7c53b0"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64.tar.gz"
|
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120174"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.linux-arm64-musl"]
|
[tools.pnpm."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:56a78c08cf22adf29e7dacb6f7f100139731693863d20fb94a7883463a62169e"
|
checksum = "sha256:56a78c08cf22adf29e7dacb6f7f100139731693863d20fb94a7883463a62169e"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64-musl.tar.gz"
|
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64-musl.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120177"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.linux-x64"]
|
[tools.pnpm."platforms.linux-x64"]
|
||||||
checksum = "sha256:74d64c1646385fb21691f32f0ab6aca1a9f5c829ba54d3cda3a24838a228e68c"
|
checksum = "sha256:74d64c1646385fb21691f32f0ab6aca1a9f5c829ba54d3cda3a24838a228e68c"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64.tar.gz"
|
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120176"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.linux-x64-musl"]
|
[tools.pnpm."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:7a0c463a09d912fba6b7d9eca0a946bc228ea50f3015a05c09a29e7e85a932d7"
|
checksum = "sha256:7a0c463a09d912fba6b7d9eca0a946bc228ea50f3015a05c09a29e7e85a932d7"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64-musl.tar.gz"
|
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64-musl.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120175"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.macos-arm64"]
|
[tools.pnpm."platforms.macos-arm64"]
|
||||||
checksum = "sha256:87c901635a14481fb30566a3749041134ffd4317bc6fe866c345b69fdf9b6b85"
|
checksum = "sha256:87c901635a14481fb30566a3749041134ffd4317bc6fe866c345b69fdf9b6b85"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-darwin-arm64.tar.gz"
|
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-darwin-arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120180"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.windows-x64"]
|
[tools.pnpm."platforms.windows-x64"]
|
||||||
checksum = "sha256:91c753435542b04859c689304fae0dd64eba6b40937cfa426a48485b712e4e9c"
|
checksum = "sha256:91c753435542b04859c689304fae0dd64eba6b40937cfa426a48485b712e4e9c"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-win32-x64.zip"
|
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-win32-x64.zip"
|
||||||
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120173"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[[tools.terragrunt]]
|
[[tools.terragrunt]]
|
||||||
@@ -320,27 +333,34 @@ backend = "aqua:gruntwork-io/terragrunt"
|
|||||||
[tools.terragrunt."platforms.linux-arm64"]
|
[tools.terragrunt."platforms.linux-arm64"]
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-arm64-musl"]
|
[tools.terragrunt."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-x64"]
|
[tools.terragrunt."platforms.linux-x64"]
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-x64-musl"]
|
[tools.terragrunt."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.macos-arm64"]
|
[tools.terragrunt."platforms.macos-arm64"]
|
||||||
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654084"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.macos-x64"]
|
[tools.terragrunt."platforms.macos-x64"]
|
||||||
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406653970"
|
||||||
|
|
||||||
[tools.terragrunt."platforms.windows-x64"]
|
[tools.terragrunt."platforms.windows-x64"]
|
||||||
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
|
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
|
||||||
|
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654412"
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 3053,
|
"android.injected.version.code" => 3054,
|
||||||
"android.injected.version.name" => "3.0.0",
|
"android.injected.version.name" => "3.0.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', track: 'beta')
|
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', track: 'beta')
|
||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 3053,
|
"android.injected.version.code" => 3054,
|
||||||
"android.injected.version.name" => "3.0.0",
|
"android.injected.version.name" => "3.0.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')
|
||||||
|
|||||||
@@ -70,7 +70,8 @@
|
|||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
enableGPUValidationMode = "1"
|
enableGPUValidationMode = "1"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES"
|
||||||
|
queueDebuggingEnabled = "NO">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>3.0.0</string>
|
<string>3.0.1</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ enum SyncMigrationTask {
|
|||||||
v20260128_CopyExifWidthHeightToAsset, // Asset table has incorrect width and height for video ratio calculations.
|
v20260128_CopyExifWidthHeightToAsset, // Asset table has incorrect width and height for video ratio calculations.
|
||||||
v20260128_ResetAssetV1, // Asset v2.5.0 has width and height information that were edited assets.
|
v20260128_ResetAssetV1, // Asset v2.5.0 has width and height information that were edited assets.
|
||||||
v20260597_ResetAssetV1AssetV2, // Assets didn't include the uploadedAt column.
|
v20260597_ResetAssetV1AssetV2, // Assets didn't include the uploadedAt column.
|
||||||
|
v20260701_ResetAlbumsV1, // Album user migration dropped the owner. Sync fresh albums from the server to re-populate them.
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyncStreamService {
|
class SyncStreamService {
|
||||||
@@ -103,6 +104,12 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runPreSyncTasks(List<String> migrations, SemVer semVer) async {
|
Future<void> _runPreSyncTasks(List<String> migrations, SemVer semVer) async {
|
||||||
|
if (!migrations.contains(SyncMigrationTask.v20260701_ResetAlbumsV1.name)) {
|
||||||
|
_logger.info("Running pre-sync task: v20260701_ResetAlbumsV1");
|
||||||
|
await _syncApiRepository.deleteSyncAck([SyncEntityType.albumV1]);
|
||||||
|
migrations.add(SyncMigrationTask.v20260701_ResetAlbumsV1.name);
|
||||||
|
}
|
||||||
|
|
||||||
if (!migrations.contains(SyncMigrationTask.v20260128_ResetExifV1.name)) {
|
if (!migrations.contains(SyncMigrationTask.v20260128_ResetExifV1.name)) {
|
||||||
_logger.info("Running pre-sync task: v20260128_ResetExifV1");
|
_logger.info("Running pre-sync task: v20260128_ResetExifV1");
|
||||||
await _syncApiRepository.deleteSyncAck([
|
await _syncApiRepository.deleteSyncAck([
|
||||||
|
|||||||
@@ -159,8 +159,6 @@ class ShareActionButton extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -173,7 +171,6 @@ class ShareActionButton extends ConsumerWidget {
|
|||||||
buildContext.pop();
|
buildContext.pop();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show download progress with a "Preparing" message
|
|
||||||
return preparingDialog;
|
return preparingDialog;
|
||||||
},
|
},
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ final Map<String, Map<String, Object?>> openApiPatches = {
|
|||||||
'sharedLinks': SharedLinksResponse(enabled: true, sidebarWeb: false).toJson(),
|
'sharedLinks': SharedLinksResponse(enabled: true, sidebarWeb: false).toJson(),
|
||||||
'cast': CastResponse(gCastEnabled: false).toJson(),
|
'cast': CastResponse(gCastEnabled: false).toJson(),
|
||||||
'albums': {'defaultAssetOrder': 'desc'},
|
'albums': {'defaultAssetOrder': 'desc'},
|
||||||
|
'recentlyAdded': RecentlyAddedResponse(sidebarWeb: false).toJson(),
|
||||||
},
|
},
|
||||||
'ServerConfigDto': {
|
'ServerConfigDto': {
|
||||||
'mapLightStyleUrl': 'https://tiles.immich.cloud/v1/style/light.json',
|
'mapLightStyleUrl': 'https://tiles.immich.cloud/v1/style/light.json',
|
||||||
|
|||||||
Generated
+4
-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: 3.0.0
|
- API version: 3.0.1
|
||||||
- Generator version: 7.22.0
|
- Generator version: 7.22.0
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
@@ -452,6 +452,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [FacialRecognitionConfig](doc//FacialRecognitionConfig.md)
|
- [FacialRecognitionConfig](doc//FacialRecognitionConfig.md)
|
||||||
- [FoldersResponse](doc//FoldersResponse.md)
|
- [FoldersResponse](doc//FoldersResponse.md)
|
||||||
- [FoldersUpdate](doc//FoldersUpdate.md)
|
- [FoldersUpdate](doc//FoldersUpdate.md)
|
||||||
|
- [HlsVideoResolution](doc//HlsVideoResolution.md)
|
||||||
- [ImageFormat](doc//ImageFormat.md)
|
- [ImageFormat](doc//ImageFormat.md)
|
||||||
- [IntegrityReport](doc//IntegrityReport.md)
|
- [IntegrityReport](doc//IntegrityReport.md)
|
||||||
- [IntegrityReportResponseDto](doc//IntegrityReportResponseDto.md)
|
- [IntegrityReportResponseDto](doc//IntegrityReportResponseDto.md)
|
||||||
@@ -545,6 +546,8 @@ Class | Method | HTTP request | Description
|
|||||||
- [RatingsUpdate](doc//RatingsUpdate.md)
|
- [RatingsUpdate](doc//RatingsUpdate.md)
|
||||||
- [ReactionLevel](doc//ReactionLevel.md)
|
- [ReactionLevel](doc//ReactionLevel.md)
|
||||||
- [ReactionType](doc//ReactionType.md)
|
- [ReactionType](doc//ReactionType.md)
|
||||||
|
- [RecentlyAddedResponse](doc//RecentlyAddedResponse.md)
|
||||||
|
- [RecentlyAddedUpdate](doc//RecentlyAddedUpdate.md)
|
||||||
- [ReleaseChannel](doc//ReleaseChannel.md)
|
- [ReleaseChannel](doc//ReleaseChannel.md)
|
||||||
- [ReleaseEventV1](doc//ReleaseEventV1.md)
|
- [ReleaseEventV1](doc//ReleaseEventV1.md)
|
||||||
- [ReleaseType](doc//ReleaseType.md)
|
- [ReleaseType](doc//ReleaseType.md)
|
||||||
|
|||||||
Generated
+3
@@ -173,6 +173,7 @@ part 'model/face_dto.dart';
|
|||||||
part 'model/facial_recognition_config.dart';
|
part 'model/facial_recognition_config.dart';
|
||||||
part 'model/folders_response.dart';
|
part 'model/folders_response.dart';
|
||||||
part 'model/folders_update.dart';
|
part 'model/folders_update.dart';
|
||||||
|
part 'model/hls_video_resolution.dart';
|
||||||
part 'model/image_format.dart';
|
part 'model/image_format.dart';
|
||||||
part 'model/integrity_report.dart';
|
part 'model/integrity_report.dart';
|
||||||
part 'model/integrity_report_response_dto.dart';
|
part 'model/integrity_report_response_dto.dart';
|
||||||
@@ -266,6 +267,8 @@ part 'model/ratings_response.dart';
|
|||||||
part 'model/ratings_update.dart';
|
part 'model/ratings_update.dart';
|
||||||
part 'model/reaction_level.dart';
|
part 'model/reaction_level.dart';
|
||||||
part 'model/reaction_type.dart';
|
part 'model/reaction_type.dart';
|
||||||
|
part 'model/recently_added_response.dart';
|
||||||
|
part 'model/recently_added_update.dart';
|
||||||
part 'model/release_channel.dart';
|
part 'model/release_channel.dart';
|
||||||
part 'model/release_event_v1.dart';
|
part 'model/release_event_v1.dart';
|
||||||
part 'model/release_type.dart';
|
part 'model/release_type.dart';
|
||||||
|
|||||||
Generated
+6
@@ -391,6 +391,8 @@ class ApiClient {
|
|||||||
return FoldersResponse.fromJson(value);
|
return FoldersResponse.fromJson(value);
|
||||||
case 'FoldersUpdate':
|
case 'FoldersUpdate':
|
||||||
return FoldersUpdate.fromJson(value);
|
return FoldersUpdate.fromJson(value);
|
||||||
|
case 'HlsVideoResolution':
|
||||||
|
return HlsVideoResolutionTypeTransformer().decode(value);
|
||||||
case 'ImageFormat':
|
case 'ImageFormat':
|
||||||
return ImageFormatTypeTransformer().decode(value);
|
return ImageFormatTypeTransformer().decode(value);
|
||||||
case 'IntegrityReport':
|
case 'IntegrityReport':
|
||||||
@@ -577,6 +579,10 @@ class ApiClient {
|
|||||||
return ReactionLevelTypeTransformer().decode(value);
|
return ReactionLevelTypeTransformer().decode(value);
|
||||||
case 'ReactionType':
|
case 'ReactionType':
|
||||||
return ReactionTypeTypeTransformer().decode(value);
|
return ReactionTypeTypeTransformer().decode(value);
|
||||||
|
case 'RecentlyAddedResponse':
|
||||||
|
return RecentlyAddedResponse.fromJson(value);
|
||||||
|
case 'RecentlyAddedUpdate':
|
||||||
|
return RecentlyAddedUpdate.fromJson(value);
|
||||||
case 'ReleaseChannel':
|
case 'ReleaseChannel':
|
||||||
return ReleaseChannelTypeTransformer().decode(value);
|
return ReleaseChannelTypeTransformer().decode(value);
|
||||||
case 'ReleaseEventV1':
|
case 'ReleaseEventV1':
|
||||||
|
|||||||
Generated
+3
@@ -106,6 +106,9 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is Colorspace) {
|
if (value is Colorspace) {
|
||||||
return ColorspaceTypeTransformer().encode(value).toString();
|
return ColorspaceTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
if (value is HlsVideoResolution) {
|
||||||
|
return HlsVideoResolutionTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
if (value is ImageFormat) {
|
if (value is ImageFormat) {
|
||||||
return ImageFormatTypeTransformer().encode(value).toString();
|
return ImageFormatTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
|||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
/// HLS video resolution
|
||||||
|
class HlsVideoResolution {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const HlsVideoResolution._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value.toString();
|
||||||
|
|
||||||
|
int toJson() => value;
|
||||||
|
|
||||||
|
static const number480 = HlsVideoResolution._(480);
|
||||||
|
static const number720 = HlsVideoResolution._(720);
|
||||||
|
static const number1080 = HlsVideoResolution._(1080);
|
||||||
|
static const number1440 = HlsVideoResolution._(1440);
|
||||||
|
static const number2160 = HlsVideoResolution._(2160);
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][HlsVideoResolution].
|
||||||
|
static const values = <HlsVideoResolution>[
|
||||||
|
number480,
|
||||||
|
number720,
|
||||||
|
number1080,
|
||||||
|
number1440,
|
||||||
|
number2160,
|
||||||
|
];
|
||||||
|
|
||||||
|
static HlsVideoResolution? fromJson(dynamic value) => HlsVideoResolutionTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<HlsVideoResolution> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <HlsVideoResolution>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = HlsVideoResolution.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [HlsVideoResolution] to int,
|
||||||
|
/// and [decode] dynamic data back to [HlsVideoResolution].
|
||||||
|
class HlsVideoResolutionTypeTransformer {
|
||||||
|
factory HlsVideoResolutionTypeTransformer() => _instance ??= const HlsVideoResolutionTypeTransformer._();
|
||||||
|
|
||||||
|
const HlsVideoResolutionTypeTransformer._();
|
||||||
|
|
||||||
|
int encode(HlsVideoResolution data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a HlsVideoResolution.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
HlsVideoResolution? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data) {
|
||||||
|
case 480: return HlsVideoResolution.number480;
|
||||||
|
case 720: return HlsVideoResolution.number720;
|
||||||
|
case 1080: return HlsVideoResolution.number1080;
|
||||||
|
case 1440: return HlsVideoResolution.number1440;
|
||||||
|
case 2160: return HlsVideoResolution.number2160;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [HlsVideoResolutionTypeTransformer] instance.
|
||||||
|
static HlsVideoResolutionTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class RecentlyAddedResponse {
|
||||||
|
/// Returns a new [RecentlyAddedResponse] instance.
|
||||||
|
RecentlyAddedResponse({
|
||||||
|
required this.sidebarWeb,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Whether the recently added page appears in the web sidebar
|
||||||
|
bool sidebarWeb;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is RecentlyAddedResponse &&
|
||||||
|
other.sidebarWeb == sidebarWeb;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(sidebarWeb.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'RecentlyAddedResponse[sidebarWeb=$sidebarWeb]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'sidebarWeb'] = this.sidebarWeb;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [RecentlyAddedResponse] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static RecentlyAddedResponse? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "RecentlyAddedResponse");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return RecentlyAddedResponse(
|
||||||
|
sidebarWeb: mapValueOfType<bool>(json, r'sidebarWeb')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<RecentlyAddedResponse> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <RecentlyAddedResponse>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = RecentlyAddedResponse.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, RecentlyAddedResponse> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, RecentlyAddedResponse>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = RecentlyAddedResponse.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of RecentlyAddedResponse-objects as value to a dart map
|
||||||
|
static Map<String, List<RecentlyAddedResponse>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<RecentlyAddedResponse>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = RecentlyAddedResponse.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'sidebarWeb',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class RecentlyAddedUpdate {
|
||||||
|
/// Returns a new [RecentlyAddedUpdate] instance.
|
||||||
|
RecentlyAddedUpdate({
|
||||||
|
this.sidebarWeb = const Optional.absent(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Whether the recently added page appears in the web sidebar
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
Optional<bool?> sidebarWeb;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is RecentlyAddedUpdate &&
|
||||||
|
other.sidebarWeb == sidebarWeb;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(sidebarWeb == null ? 0 : sidebarWeb!.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'RecentlyAddedUpdate[sidebarWeb=$sidebarWeb]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
if (this.sidebarWeb.isPresent) {
|
||||||
|
final value = this.sidebarWeb.value;
|
||||||
|
json[r'sidebarWeb'] = value;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [RecentlyAddedUpdate] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static RecentlyAddedUpdate? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "RecentlyAddedUpdate");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return RecentlyAddedUpdate(
|
||||||
|
sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType<bool>(json, r'sidebarWeb')) : const Optional.absent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<RecentlyAddedUpdate> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <RecentlyAddedUpdate>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = RecentlyAddedUpdate.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, RecentlyAddedUpdate> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, RecentlyAddedUpdate>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = RecentlyAddedUpdate.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of RecentlyAddedUpdate-objects as value to a dart map
|
||||||
|
static Map<String, List<RecentlyAddedUpdate>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<RecentlyAddedUpdate>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = RecentlyAddedUpdate.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -14,26 +14,40 @@ class SystemConfigFFmpegRealtimeDto {
|
|||||||
/// Returns a new [SystemConfigFFmpegRealtimeDto] instance.
|
/// Returns a new [SystemConfigFFmpegRealtimeDto] instance.
|
||||||
SystemConfigFFmpegRealtimeDto({
|
SystemConfigFFmpegRealtimeDto({
|
||||||
required this.enabled,
|
required this.enabled,
|
||||||
|
this.resolutions = const [],
|
||||||
|
this.videoCodecs = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Enable real-time HLS transcoding (alpha)
|
/// Enable real-time HLS transcoding (alpha)
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
|
||||||
|
/// Resolutions to use for real-time HLS transcoding
|
||||||
|
List<HlsVideoResolution> resolutions;
|
||||||
|
|
||||||
|
/// Video codecs to use for real-time HLS transcoding
|
||||||
|
List<VideoCodec> videoCodecs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegRealtimeDto &&
|
bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegRealtimeDto &&
|
||||||
other.enabled == enabled;
|
other.enabled == enabled &&
|
||||||
|
_deepEquality.equals(other.resolutions, resolutions) &&
|
||||||
|
_deepEquality.equals(other.videoCodecs, videoCodecs);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(enabled.hashCode);
|
(enabled.hashCode) +
|
||||||
|
(resolutions.hashCode) +
|
||||||
|
(videoCodecs.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'SystemConfigFFmpegRealtimeDto[enabled=$enabled]';
|
String toString() => 'SystemConfigFFmpegRealtimeDto[enabled=$enabled, resolutions=$resolutions, videoCodecs=$videoCodecs]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'enabled'] = this.enabled;
|
json[r'enabled'] = this.enabled;
|
||||||
|
json[r'resolutions'] = this.resolutions;
|
||||||
|
json[r'videoCodecs'] = this.videoCodecs;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +61,8 @@ class SystemConfigFFmpegRealtimeDto {
|
|||||||
|
|
||||||
return SystemConfigFFmpegRealtimeDto(
|
return SystemConfigFFmpegRealtimeDto(
|
||||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||||
|
resolutions: HlsVideoResolution.listFromJson(json[r'resolutions']),
|
||||||
|
videoCodecs: VideoCodec.listFromJson(json[r'videoCodecs']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -95,6 +111,8 @@ class SystemConfigFFmpegRealtimeDto {
|
|||||||
/// 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>{
|
||||||
'enabled',
|
'enabled',
|
||||||
|
'resolutions',
|
||||||
|
'videoCodecs',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-1
@@ -22,6 +22,7 @@ class UserPreferencesResponseDto {
|
|||||||
required this.people,
|
required this.people,
|
||||||
required this.purchase,
|
required this.purchase,
|
||||||
required this.ratings,
|
required this.ratings,
|
||||||
|
required this.recentlyAdded,
|
||||||
required this.sharedLinks,
|
required this.sharedLinks,
|
||||||
required this.tags,
|
required this.tags,
|
||||||
});
|
});
|
||||||
@@ -44,6 +45,8 @@ class UserPreferencesResponseDto {
|
|||||||
|
|
||||||
RatingsResponse ratings;
|
RatingsResponse ratings;
|
||||||
|
|
||||||
|
RecentlyAddedResponse recentlyAdded;
|
||||||
|
|
||||||
SharedLinksResponse sharedLinks;
|
SharedLinksResponse sharedLinks;
|
||||||
|
|
||||||
TagsResponse tags;
|
TagsResponse tags;
|
||||||
@@ -59,6 +62,7 @@ class UserPreferencesResponseDto {
|
|||||||
other.people == people &&
|
other.people == people &&
|
||||||
other.purchase == purchase &&
|
other.purchase == purchase &&
|
||||||
other.ratings == ratings &&
|
other.ratings == ratings &&
|
||||||
|
other.recentlyAdded == recentlyAdded &&
|
||||||
other.sharedLinks == sharedLinks &&
|
other.sharedLinks == sharedLinks &&
|
||||||
other.tags == tags;
|
other.tags == tags;
|
||||||
|
|
||||||
@@ -74,11 +78,12 @@ class UserPreferencesResponseDto {
|
|||||||
(people.hashCode) +
|
(people.hashCode) +
|
||||||
(purchase.hashCode) +
|
(purchase.hashCode) +
|
||||||
(ratings.hashCode) +
|
(ratings.hashCode) +
|
||||||
|
(recentlyAdded.hashCode) +
|
||||||
(sharedLinks.hashCode) +
|
(sharedLinks.hashCode) +
|
||||||
(tags.hashCode);
|
(tags.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'UserPreferencesResponseDto[albums=$albums, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]';
|
String toString() => 'UserPreferencesResponseDto[albums=$albums, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, recentlyAdded=$recentlyAdded, sharedLinks=$sharedLinks, tags=$tags]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -91,6 +96,7 @@ class UserPreferencesResponseDto {
|
|||||||
json[r'people'] = this.people;
|
json[r'people'] = this.people;
|
||||||
json[r'purchase'] = this.purchase;
|
json[r'purchase'] = this.purchase;
|
||||||
json[r'ratings'] = this.ratings;
|
json[r'ratings'] = this.ratings;
|
||||||
|
json[r'recentlyAdded'] = this.recentlyAdded;
|
||||||
json[r'sharedLinks'] = this.sharedLinks;
|
json[r'sharedLinks'] = this.sharedLinks;
|
||||||
json[r'tags'] = this.tags;
|
json[r'tags'] = this.tags;
|
||||||
return json;
|
return json;
|
||||||
@@ -114,6 +120,7 @@ class UserPreferencesResponseDto {
|
|||||||
people: PeopleResponse.fromJson(json[r'people'])!,
|
people: PeopleResponse.fromJson(json[r'people'])!,
|
||||||
purchase: PurchaseResponse.fromJson(json[r'purchase'])!,
|
purchase: PurchaseResponse.fromJson(json[r'purchase'])!,
|
||||||
ratings: RatingsResponse.fromJson(json[r'ratings'])!,
|
ratings: RatingsResponse.fromJson(json[r'ratings'])!,
|
||||||
|
recentlyAdded: RecentlyAddedResponse.fromJson(json[r'recentlyAdded'])!,
|
||||||
sharedLinks: SharedLinksResponse.fromJson(json[r'sharedLinks'])!,
|
sharedLinks: SharedLinksResponse.fromJson(json[r'sharedLinks'])!,
|
||||||
tags: TagsResponse.fromJson(json[r'tags'])!,
|
tags: TagsResponse.fromJson(json[r'tags'])!,
|
||||||
);
|
);
|
||||||
@@ -172,6 +179,7 @@ class UserPreferencesResponseDto {
|
|||||||
'people',
|
'people',
|
||||||
'purchase',
|
'purchase',
|
||||||
'ratings',
|
'ratings',
|
||||||
|
'recentlyAdded',
|
||||||
'sharedLinks',
|
'sharedLinks',
|
||||||
'tags',
|
'tags',
|
||||||
};
|
};
|
||||||
|
|||||||
+17
-1
@@ -23,6 +23,7 @@ class UserPreferencesUpdateDto {
|
|||||||
this.people = const Optional.absent(),
|
this.people = const Optional.absent(),
|
||||||
this.purchase = const Optional.absent(),
|
this.purchase = const Optional.absent(),
|
||||||
this.ratings = const Optional.absent(),
|
this.ratings = const Optional.absent(),
|
||||||
|
this.recentlyAdded = const Optional.absent(),
|
||||||
this.sharedLinks = const Optional.absent(),
|
this.sharedLinks = const Optional.absent(),
|
||||||
this.tags = const Optional.absent(),
|
this.tags = const Optional.absent(),
|
||||||
});
|
});
|
||||||
@@ -107,6 +108,14 @@ class UserPreferencesUpdateDto {
|
|||||||
///
|
///
|
||||||
Optional<RatingsUpdate?> ratings;
|
Optional<RatingsUpdate?> ratings;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
Optional<RecentlyAddedUpdate?> recentlyAdded;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
@@ -135,6 +144,7 @@ class UserPreferencesUpdateDto {
|
|||||||
other.people == people &&
|
other.people == people &&
|
||||||
other.purchase == purchase &&
|
other.purchase == purchase &&
|
||||||
other.ratings == ratings &&
|
other.ratings == ratings &&
|
||||||
|
other.recentlyAdded == recentlyAdded &&
|
||||||
other.sharedLinks == sharedLinks &&
|
other.sharedLinks == sharedLinks &&
|
||||||
other.tags == tags;
|
other.tags == tags;
|
||||||
|
|
||||||
@@ -151,11 +161,12 @@ class UserPreferencesUpdateDto {
|
|||||||
(people == null ? 0 : people!.hashCode) +
|
(people == null ? 0 : people!.hashCode) +
|
||||||
(purchase == null ? 0 : purchase!.hashCode) +
|
(purchase == null ? 0 : purchase!.hashCode) +
|
||||||
(ratings == null ? 0 : ratings!.hashCode) +
|
(ratings == null ? 0 : ratings!.hashCode) +
|
||||||
|
(recentlyAdded == null ? 0 : recentlyAdded!.hashCode) +
|
||||||
(sharedLinks == null ? 0 : sharedLinks!.hashCode) +
|
(sharedLinks == null ? 0 : sharedLinks!.hashCode) +
|
||||||
(tags == null ? 0 : tags!.hashCode);
|
(tags == null ? 0 : tags!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'UserPreferencesUpdateDto[albums=$albums, avatar=$avatar, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]';
|
String toString() => 'UserPreferencesUpdateDto[albums=$albums, avatar=$avatar, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, recentlyAdded=$recentlyAdded, sharedLinks=$sharedLinks, tags=$tags]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -199,6 +210,10 @@ class UserPreferencesUpdateDto {
|
|||||||
final value = this.ratings.value;
|
final value = this.ratings.value;
|
||||||
json[r'ratings'] = value;
|
json[r'ratings'] = value;
|
||||||
}
|
}
|
||||||
|
if (this.recentlyAdded.isPresent) {
|
||||||
|
final value = this.recentlyAdded.value;
|
||||||
|
json[r'recentlyAdded'] = value;
|
||||||
|
}
|
||||||
if (this.sharedLinks.isPresent) {
|
if (this.sharedLinks.isPresent) {
|
||||||
final value = this.sharedLinks.value;
|
final value = this.sharedLinks.value;
|
||||||
json[r'sharedLinks'] = value;
|
json[r'sharedLinks'] = value;
|
||||||
@@ -229,6 +244,7 @@ class UserPreferencesUpdateDto {
|
|||||||
people: json.containsKey(r'people') ? Optional.present(PeopleUpdate.fromJson(json[r'people'])) : const Optional.absent(),
|
people: json.containsKey(r'people') ? Optional.present(PeopleUpdate.fromJson(json[r'people'])) : const Optional.absent(),
|
||||||
purchase: json.containsKey(r'purchase') ? Optional.present(PurchaseUpdate.fromJson(json[r'purchase'])) : const Optional.absent(),
|
purchase: json.containsKey(r'purchase') ? Optional.present(PurchaseUpdate.fromJson(json[r'purchase'])) : const Optional.absent(),
|
||||||
ratings: json.containsKey(r'ratings') ? Optional.present(RatingsUpdate.fromJson(json[r'ratings'])) : const Optional.absent(),
|
ratings: json.containsKey(r'ratings') ? Optional.present(RatingsUpdate.fromJson(json[r'ratings'])) : const Optional.absent(),
|
||||||
|
recentlyAdded: json.containsKey(r'recentlyAdded') ? Optional.present(RecentlyAddedUpdate.fromJson(json[r'recentlyAdded'])) : const Optional.absent(),
|
||||||
sharedLinks: json.containsKey(r'sharedLinks') ? Optional.present(SharedLinksUpdate.fromJson(json[r'sharedLinks'])) : const Optional.absent(),
|
sharedLinks: json.containsKey(r'sharedLinks') ? Optional.present(SharedLinksUpdate.fromJson(json[r'sharedLinks'])) : const Optional.absent(),
|
||||||
tags: json.containsKey(r'tags') ? Optional.present(TagsUpdate.fromJson(json[r'tags'])) : const Optional.absent(),
|
tags: json.containsKey(r'tags') ? Optional.present(TagsUpdate.fromJson(json[r'tags'])) : const Optional.absent(),
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -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: 3.0.0+3053
|
version: 3.0.1+3054
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.12.0 <4.0.0'
|
sdk: '>=3.12.0 <4.0.0'
|
||||||
|
|||||||
@@ -16206,7 +16206,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -19120,6 +19120,17 @@
|
|||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"HlsVideoResolution": {
|
||||||
|
"description": "HLS video resolution",
|
||||||
|
"enum": [
|
||||||
|
480,
|
||||||
|
720,
|
||||||
|
1080,
|
||||||
|
1440,
|
||||||
|
2160
|
||||||
|
],
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"ImageFormat": {
|
"ImageFormat": {
|
||||||
"description": "Image format",
|
"description": "Image format",
|
||||||
"enum": [
|
"enum": [
|
||||||
@@ -21991,6 +22002,27 @@
|
|||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"RecentlyAddedResponse": {
|
||||||
|
"properties": {
|
||||||
|
"sidebarWeb": {
|
||||||
|
"description": "Whether the recently added page appears in the web sidebar",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"sidebarWeb"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"RecentlyAddedUpdate": {
|
||||||
|
"properties": {
|
||||||
|
"sidebarWeb": {
|
||||||
|
"description": "Whether the recently added page appears in the web sidebar",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ReleaseChannel": {
|
"ReleaseChannel": {
|
||||||
"description": "Release channel",
|
"description": "Release channel",
|
||||||
"enum": [
|
"enum": [
|
||||||
@@ -25733,10 +25765,26 @@
|
|||||||
"enabled": {
|
"enabled": {
|
||||||
"description": "Enable real-time HLS transcoding (alpha)",
|
"description": "Enable real-time HLS transcoding (alpha)",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"description": "Resolutions to use for real-time HLS transcoding",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/HlsVideoResolution"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"videoCodecs": {
|
||||||
|
"description": "Video codecs to use for real-time HLS transcoding",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/VideoCodec"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"enabled"
|
"enabled",
|
||||||
|
"resolutions",
|
||||||
|
"videoCodecs"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
@@ -27525,6 +27573,9 @@
|
|||||||
"ratings": {
|
"ratings": {
|
||||||
"$ref": "#/components/schemas/RatingsResponse"
|
"$ref": "#/components/schemas/RatingsResponse"
|
||||||
},
|
},
|
||||||
|
"recentlyAdded": {
|
||||||
|
"$ref": "#/components/schemas/RecentlyAddedResponse"
|
||||||
|
},
|
||||||
"sharedLinks": {
|
"sharedLinks": {
|
||||||
"$ref": "#/components/schemas/SharedLinksResponse"
|
"$ref": "#/components/schemas/SharedLinksResponse"
|
||||||
},
|
},
|
||||||
@@ -27542,6 +27593,7 @@
|
|||||||
"people",
|
"people",
|
||||||
"purchase",
|
"purchase",
|
||||||
"ratings",
|
"ratings",
|
||||||
|
"recentlyAdded",
|
||||||
"sharedLinks",
|
"sharedLinks",
|
||||||
"tags"
|
"tags"
|
||||||
],
|
],
|
||||||
@@ -27579,6 +27631,9 @@
|
|||||||
"ratings": {
|
"ratings": {
|
||||||
"$ref": "#/components/schemas/RatingsUpdate"
|
"$ref": "#/components/schemas/RatingsUpdate"
|
||||||
},
|
},
|
||||||
|
"recentlyAdded": {
|
||||||
|
"$ref": "#/components/schemas/RecentlyAddedUpdate"
|
||||||
|
},
|
||||||
"sharedLinks": {
|
"sharedLinks": {
|
||||||
"$ref": "#/components/schemas/SharedLinksUpdate"
|
"$ref": "#/components/schemas/SharedLinksUpdate"
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-monorepo",
|
"name": "immich-monorepo",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "Monorepo for Immich",
|
"description": "Monorepo for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 3.0.0
|
* 3.0.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
|
||||||
*/
|
*/
|
||||||
@@ -342,6 +342,10 @@ export type RatingsResponse = {
|
|||||||
/** Whether ratings are enabled */
|
/** Whether ratings are enabled */
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
export type RecentlyAddedResponse = {
|
||||||
|
/** Whether the recently added page appears in the web sidebar */
|
||||||
|
sidebarWeb: boolean;
|
||||||
|
};
|
||||||
export type SharedLinksResponse = {
|
export type SharedLinksResponse = {
|
||||||
/** Whether shared links are enabled */
|
/** Whether shared links are enabled */
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -364,6 +368,7 @@ export type UserPreferencesResponseDto = {
|
|||||||
people: PeopleResponse;
|
people: PeopleResponse;
|
||||||
purchase: PurchaseResponse;
|
purchase: PurchaseResponse;
|
||||||
ratings: RatingsResponse;
|
ratings: RatingsResponse;
|
||||||
|
recentlyAdded: RecentlyAddedResponse;
|
||||||
sharedLinks: SharedLinksResponse;
|
sharedLinks: SharedLinksResponse;
|
||||||
tags: TagsResponse;
|
tags: TagsResponse;
|
||||||
};
|
};
|
||||||
@@ -421,6 +426,10 @@ export type RatingsUpdate = {
|
|||||||
/** Whether ratings are enabled */
|
/** Whether ratings are enabled */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
|
export type RecentlyAddedUpdate = {
|
||||||
|
/** Whether the recently added page appears in the web sidebar */
|
||||||
|
sidebarWeb?: boolean;
|
||||||
|
};
|
||||||
export type SharedLinksUpdate = {
|
export type SharedLinksUpdate = {
|
||||||
/** Whether shared links are enabled */
|
/** Whether shared links are enabled */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
@@ -444,6 +453,7 @@ export type UserPreferencesUpdateDto = {
|
|||||||
people?: PeopleUpdate;
|
people?: PeopleUpdate;
|
||||||
purchase?: PurchaseUpdate;
|
purchase?: PurchaseUpdate;
|
||||||
ratings?: RatingsUpdate;
|
ratings?: RatingsUpdate;
|
||||||
|
recentlyAdded?: RecentlyAddedUpdate;
|
||||||
sharedLinks?: SharedLinksUpdate;
|
sharedLinks?: SharedLinksUpdate;
|
||||||
tags?: TagsUpdate;
|
tags?: TagsUpdate;
|
||||||
};
|
};
|
||||||
@@ -2292,6 +2302,10 @@ export type SystemConfigBackupsDto = {
|
|||||||
export type SystemConfigFFmpegRealtimeDto = {
|
export type SystemConfigFFmpegRealtimeDto = {
|
||||||
/** Enable real-time HLS transcoding (alpha) */
|
/** Enable real-time HLS transcoding (alpha) */
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
/** Resolutions to use for real-time HLS transcoding */
|
||||||
|
resolutions: HlsVideoResolution[];
|
||||||
|
/** Video codecs to use for real-time HLS transcoding */
|
||||||
|
videoCodecs: VideoCodec[];
|
||||||
};
|
};
|
||||||
export type SystemConfigFFmpegDto = {
|
export type SystemConfigFFmpegDto = {
|
||||||
accel: TranscodeHWAccel;
|
accel: TranscodeHWAccel;
|
||||||
@@ -7619,6 +7633,13 @@ export enum CQMode {
|
|||||||
Cqp = "cqp",
|
Cqp = "cqp",
|
||||||
Icq = "icq"
|
Icq = "icq"
|
||||||
}
|
}
|
||||||
|
export enum HlsVideoResolution {
|
||||||
|
$480 = 480,
|
||||||
|
$720 = 720,
|
||||||
|
$1080 = 1080,
|
||||||
|
$1440 = 1440,
|
||||||
|
$2160 = 2160
|
||||||
|
}
|
||||||
export enum ToneMapping {
|
export enum ToneMapping {
|
||||||
Hable = "hable",
|
Hable = "hable",
|
||||||
Mobius = "mobius",
|
Mobius = "mobius",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_vi_VN.md">Tiếng Việt</a>
|
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<p align="center">
|
||||||
|
<br/>
|
||||||
|
<a href="https://opensource.org/license/agpl-v3"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: AGPLv3"></a>
|
||||||
|
<a href="https://discord.immich.app">
|
||||||
|
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" alt="Discord"/>
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="../design/immich-logo-stacked-light.svg" width="300" title="Immich">
|
||||||
|
</p>
|
||||||
|
<h3 align="center">ഫോട്ടോകളും വീഡിയോകളും കൈകാര്യം ചെയ്യുന്നതിനുള്ള ഉയർന്ന കാര്യക്ഷമതയുള്ള സെൽഫ്-ഹോസ്റ്റഡ് ആപ്ലിക്കേഷൻ</h3>
|
||||||
|
<br/>
|
||||||
|
<a href="https://immich.app">
|
||||||
|
<img src="../design/immich-screenshots.png" title="പ്രധാന സ്ക്രീൻഷോട്ട്">
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="../README.md">English</a>
|
||||||
|
<a href="README_ca_ES.md">Català</a>
|
||||||
|
<a href="README_es_ES.md">Español</a>
|
||||||
|
<a href="README_fr_FR.md">Français</a>
|
||||||
|
<a href="README_it_IT.md">Italiano</a>
|
||||||
|
<a href="README_ja_JP.md">日本語</a>
|
||||||
|
<a href="README_ko_KR.md">한국어</a>
|
||||||
|
<a href="README_de_DE.md">Deutsch</a>
|
||||||
|
<a href="README_nl_NL.md">Nederlands</a>
|
||||||
|
<a href="README_tr_TR.md">Türkçe</a>
|
||||||
|
<a href="README_zh_CN.md">简体中文</a>
|
||||||
|
<a href="README_zh_TW.md">正體中文</a>
|
||||||
|
<a href="README_uk_UA.md">Українська</a>
|
||||||
|
<a href="README_ru_RU.md">Русский</a>
|
||||||
|
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||||
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
|
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||||
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> ⚠️ നിങ്ങളുടെ വിലയേറിയ ഫോട്ടോകൾക്കും വീഡിയോകൾക്കും എല്ലായ്പ്പോഴും [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ബാക്കപ്പ് പ്ലാൻ പിന്തുടരുക!
|
||||||
|
>
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> ഇൻസ്റ്റാളേഷൻ ഗൈഡുകൾ ഉൾപ്പെടെയുള്ള പ്രധാന ഡോക്യുമെന്റേഷൻ https://immich.app/ എന്ന വെബ്സൈറ്റിൽ നിങ്ങൾക്ക് കണ്ടെത്താം.
|
||||||
|
|
||||||
|
## ലിങ്കുകൾ
|
||||||
|
|
||||||
|
- [ഡോക്യുമെന്റേഷൻ](https://docs.immich.app/)
|
||||||
|
- [വിവരങ്ങൾ](https://docs.immich.app/overview/introduction)
|
||||||
|
- [ഇൻസ്റ്റാളേഷൻ](https://docs.immich.app/install/requirements)
|
||||||
|
- [റോഡ്മാപ്പ്](https://immich.app/roadmap)
|
||||||
|
- [ഡെമോ](#ഡെമോ)
|
||||||
|
- [സവിശേഷതകൾ](#സവിശേഷതകൾ)
|
||||||
|
- [വിവർത്തനങ്ങൾ](https://docs.immich.app/developer/translations)
|
||||||
|
- [സംഭാവന നൽകൽ](https://docs.immich.app/overview/support-the-project)
|
||||||
|
|
||||||
|
## ഡെമോ
|
||||||
|
|
||||||
|
ഡെമോ വെബ്സൈറ്റ് [ഇവിടെ](https://demo.immich.app) ആക്സസ് ചെയ്യാം. മൊബൈൽ ആപ്പിന്റെ ഡെമോ കാണാൻ ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്ത ശേഷം, `Server Endpoint URL` എന്ന സെറ്റിങ്ങിൽ നിങ്ങൾക്ക് `https://demo.immich.app` ഉപയോഗിക്കാം.
|
||||||
|
|
||||||
|
### ലോഗിൻ വിവരങ്ങൾ
|
||||||
|
|
||||||
|
| ഇമെയിൽ | പാസ്വേഡ് |
|
||||||
|
| --------------- | -------- |
|
||||||
|
| demo@immich.app | demo |
|
||||||
|
|
||||||
|
## സവിശേഷതകൾ
|
||||||
|
|
||||||
|
| സവിശേഷതകൾ | മൊബൈൽ | വെബ് |
|
||||||
|
| :-------------------------------------------------- | ------ | --- |
|
||||||
|
| ഫോട്ടോകളും വീഡിയോകളും അപ്ലോഡ് ചെയ്യാനും കാണാനും സാധിക്കും | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| ആപ്പ് തുറക്കുമ്പോൾ ഓട്ടോമാറ്റിക് ബാക്കപ്പ് | ഉണ്ട് | N/A |
|
||||||
|
| ഫയലുകളുടെ ഇരട്ടിപ്പ് (duplication) തടയുന്നു | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| ബാക്കപ്പിനായി നിർദ്ദിഷ്ട ആൽബങ്ങൾ തിരഞ്ഞെടുക്കാം | ഉണ്ട് | N/A |
|
||||||
|
| ഫോട്ടോകളും വീഡിയോകളും ലോക്കൽ ഡിവൈസിലേക്ക് ഡൗൺലോഡ് ചെയ്യാം | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| ഒന്നിലധികം ഉപയോക്താക്കൾക്കുള്ള പിന്തുണ (Multi-user) | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| ആൽബങ്ങളും പങ്കിട്ട ആൽബങ്ങളും | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| സ്ക്രബ്ബ് ചെയ്യാവുന്ന/വലിച്ചിഴക്കാവുന്ന സ്ക്രോൾബാർ | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| റോ (RAW) ഫോർമാറ്റുകൾക്കുള്ള പിന്തുണ | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| മെറ്റാഡാറ്റ കാഴ്ച (EXIF, മാപ്പ്) | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| മെറ്റാഡാറ്റ, ഒബ്ജക്റ്റുകൾ, മുഖങ്ങൾ, CLIP എന്നിവ ഉപയോഗിച്ച് തിരയാം | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| അഡ്മിനിസ്ട്രേറ്റീവ് പ്രവർത്തനങ്ങൾ (യൂസർ മാനേജ്മെന്റ്) | ഇല്ല | ഉണ്ട് |
|
||||||
|
| ബാക്ക്ഗ്രൗണ്ട് ബാക്കപ്പ് | ഉണ്ട് | N/A |
|
||||||
|
| വിർച്വൽ സ്ക്രോൾ | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| OAuth സപ്പോർട്ട് | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| API കീകൾ | N/A | ഉണ്ട് |
|
||||||
|
| ലൈവ് ഫോട്ടോ/മോഷൻ ഫോട്ടോ ബാക്കപ്പും പ്ലേബാക്കും | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| 360 ഡിഗ്രി ഇമേജ് ഡിസ്പ്ലേ പിന്തുണ | ഇല്ല | ഉണ്ട് |
|
||||||
|
| ഉപയോക്താവ് നിർവചിക്കുന്ന സ്റ്റോറേജ് ഘടന | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| പബ്ലിക് ഷെയറിംഗ് | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| ആർക്കൈവും പ്രിയപ്പെട്ടവയും (Favorites) | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| ആഗോള മാപ്പ് (Global Map) | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| പങ്കാളി പങ്കിടൽ (Partner Sharing) | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| മുഖം തിരിച്ചറിയലും ക്ലസ്റ്ററിംഗും | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| ഓർമ്മകൾ (Memories - x വർഷങ്ങൾക്ക് മുമ്പ്) | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| ഓഫ്ലൈൻ പിന്തുണ | ഉണ്ട് | ഇല്ല |
|
||||||
|
| റീഡ്-ഒൺലി ഗാലറി | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| അടുക്കിവെച്ച ഫോട്ടോകൾ (Stacked Photos) | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
| ടാഗുകൾ | ഇല്ല | ഉണ്ട് |
|
||||||
|
| ഫോൾഡർ കാഴ്ച | ഉണ്ട് | ഉണ്ട് |
|
||||||
|
|
||||||
|
## വിവർത്തനങ്ങൾ
|
||||||
|
|
||||||
|
വിവർത്തനങ്ങളെക്കുറിച്ച് കൂടുതൽ [ഇവിടെ](https://docs.immich.app/developer/translations) വായിക്കാം.
|
||||||
|
|
||||||
|
<a href="https://hosted.weblate.org/engage/immich/">
|
||||||
|
<img src="https://hosted.weblate.org/widget/immich/immich/multi-auto.svg" alt="Translation status" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## റെപ്പോസിറ്ററി പ്രവർത്തനം
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## സ്റ്റാർ ചരിത്രം
|
||||||
|
|
||||||
|
<a href="https://star-history.com/#immich-app/immich&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=date&theme=dark" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=date" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=immich-app/immich&type=date" width="100%" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## സംഭാവന ചെയ്തവർ
|
||||||
|
|
||||||
|
<a href="https://github.com/immich-app/immich/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
|
||||||
|
</a>
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_vi_VN.md">Tiếng Việt</a>
|
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_vi_VN.md">Tiếng Việt</a>
|
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_vi_VN.md">Tiếng Việt</a>
|
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<a href="README_sv_SE.md">Svenska</a>
|
<a href="README_sv_SE.md">Svenska</a>
|
||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_vi_VN.md">Tiếng Việt</a>
|
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_vi_VN.md">Tiếng Việt</a>
|
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_vi_VN.md">Tiếng Việt</a>
|
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
<a href="README_ar_JO.md">العربية</a>
|
<a href="README_ar_JO.md">العربية</a>
|
||||||
<a href="README_vi_VN.md">Tiếng Việt</a>
|
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
FROM ghcr.io/immich-app/base-server-dev:202606161235@sha256:9f88b07acc8b7bf37a1dd3d5a19193f664443eaaab4e08e9f9341414c5e4b23f AS builder
|
FROM ghcr.io/immich-app/base-server-dev:202607061334@sha256:c83ef9c6d7f5e7f6a276fe96647484d46b7bf6cacc1e35b823dd1d7a1aeda3b8 AS builder
|
||||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||||
CI=1 \
|
CI=1 \
|
||||||
COREPACK_HOME=/tmp \
|
COREPACK_HOME=/tmp \
|
||||||
@@ -56,7 +56,7 @@ FROM builder AS plugins
|
|||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
COPY --from=ghcr.io/jdx/mise:2026.6.10@sha256:f57ac375a262f52f8ac3f9101348dbff2187d5e4b59612154f2f2808dbe46ef6 /usr/local/bin/mise /usr/local/bin/mise
|
COPY --from=ghcr.io/jdx/mise:2026.6.14@sha256:b8f8c20fc3308f8b1d00ccca2bc968e4e208af1c5c1069e1ad9753baa099acff /usr/local/bin/mise /usr/local/bin/mise
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY ./mise.toml ./mise.toml
|
COPY ./mise.toml ./mise.toml
|
||||||
@@ -80,7 +80,7 @@ RUN --mount=type=cache,id=pnpm-packages,target=/buildcache/pnpm-store \
|
|||||||
--mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \
|
--mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \
|
||||||
mise //:plugins
|
mise //:plugins
|
||||||
|
|
||||||
FROM ghcr.io/immich-app/base-server-prod:202606161235@sha256:c6d59e3923f548d29a212b4dc51b6281a722cfa1da7972a009c0f3830f5762d6
|
FROM ghcr.io/immich-app/base-server-prod:202607061334@sha256:c07ef6510ff2ccb6ead28434ade7c7a0c08aef33d7d3466b14beccdd431e86f7
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# dev build
|
# dev build
|
||||||
FROM ghcr.io/immich-app/base-server-dev:202606161235@sha256:9f88b07acc8b7bf37a1dd3d5a19193f664443eaaab4e08e9f9341414c5e4b23f AS dev
|
FROM ghcr.io/immich-app/base-server-dev:202607061334@sha256:c83ef9c6d7f5e7f6a276fe96647484d46b7bf6cacc1e35b823dd1d7a1aeda3b8 AS dev
|
||||||
|
|
||||||
|
|
||||||
COPY --from=ghcr.io/jdx/mise:2026.6.10@sha256:f57ac375a262f52f8ac3f9101348dbff2187d5e4b59612154f2f2808dbe46ef6 /usr/local/bin/mise /usr/local/bin/mise
|
COPY --from=ghcr.io/jdx/mise:2026.6.14@sha256:b8f8c20fc3308f8b1d00ccca2bc968e4e208af1c5c1069e1ad9753baa099acff /usr/local/bin/mise /usr/local/bin/mise
|
||||||
|
|
||||||
RUN echo "devdir=/buildcache/node-gyp" >> /usr/local/etc/npmrc && \
|
RUN echo "devdir=/buildcache/node-gyp" >> /usr/local/etc/npmrc && \
|
||||||
echo "store-dir=/buildcache/pnpm-store" >> /usr/local/etc/npmrc && \
|
echo "store-dir=/buildcache/pnpm-store" >> /usr/local/etc/npmrc && \
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
AudioCodec,
|
AudioCodec,
|
||||||
Colorspace,
|
Colorspace,
|
||||||
CQMode,
|
CQMode,
|
||||||
|
HlsVideoResolution,
|
||||||
ImageFormat,
|
ImageFormat,
|
||||||
LogLevel,
|
LogLevel,
|
||||||
OAuthTokenEndpointAuthMethod,
|
OAuthTokenEndpointAuthMethod,
|
||||||
@@ -48,6 +49,8 @@ export type SystemConfig = {
|
|||||||
tonemap: ToneMapping;
|
tonemap: ToneMapping;
|
||||||
realtime: {
|
realtime: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
videoCodecs: VideoCodec[];
|
||||||
|
resolutions: HlsVideoResolution[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
integrityChecks: {
|
integrityChecks: {
|
||||||
@@ -247,6 +250,8 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||||||
accelDecode: true,
|
accelDecode: true,
|
||||||
realtime: {
|
realtime: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
videoCodecs: [VideoCodec.H264, VideoCodec.Hevc],
|
||||||
|
resolutions: [HlsVideoResolution.p480, HlsVideoResolution.p720, HlsVideoResolution.p1080],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
integrityChecks: {
|
integrityChecks: {
|
||||||
|
|||||||
+60
-9
@@ -235,14 +235,65 @@ export const HLS_PLAYLIST_CONTENT_TYPE = 'application/vnd.apple.mpegurl';
|
|||||||
export const HLS_SEGMENT_DURATION = 2;
|
export const HLS_SEGMENT_DURATION = 2;
|
||||||
export const HLS_SEGMENT_FILENAME_REGEX = /^seg_(\d+)\.m4s$/;
|
export const HLS_SEGMENT_FILENAME_REGEX = /^seg_(\d+)\.m4s$/;
|
||||||
export const HLS_VARIANTS = [
|
export const HLS_VARIANTS = [
|
||||||
{ resolution: 480, codec: VideoCodec.Av1, bitrate: 1_000_000, codecString: 'av01.0.04M.08' },
|
{ resolution: 480, codec: VideoCodec.Av1, bitrate: 1_000_000 },
|
||||||
{ resolution: 480, codec: VideoCodec.Hevc, bitrate: 1_200_000, codecString: 'hvc1.1.6.L90.B0' },
|
{ resolution: 480, codec: VideoCodec.Hevc, bitrate: 1_200_000 },
|
||||||
{ resolution: 480, codec: VideoCodec.H264, bitrate: 2_500_000, codecString: 'avc1.64001e' },
|
{ resolution: 480, codec: VideoCodec.H264, bitrate: 2_500_000 },
|
||||||
{ resolution: 720, codec: VideoCodec.Av1, bitrate: 2_000_000, codecString: 'av01.0.08M.08' },
|
{ resolution: 720, codec: VideoCodec.Av1, bitrate: 2_000_000 },
|
||||||
{ resolution: 720, codec: VideoCodec.Hevc, bitrate: 2_500_000, codecString: 'hvc1.1.6.L93.B0' },
|
{ resolution: 720, codec: VideoCodec.Hevc, bitrate: 2_500_000 },
|
||||||
{ resolution: 720, codec: VideoCodec.H264, bitrate: 5_000_000, codecString: 'avc1.64001f' },
|
{ resolution: 720, codec: VideoCodec.H264, bitrate: 5_000_000 },
|
||||||
{ resolution: 1080, codec: VideoCodec.Av1, bitrate: 4_000_000, codecString: 'av01.0.09M.08' },
|
{ resolution: 1080, codec: VideoCodec.Av1, bitrate: 4_000_000 },
|
||||||
{ resolution: 1080, codec: VideoCodec.Hevc, bitrate: 4_500_000, codecString: 'hvc1.1.6.L120.B0' },
|
{ resolution: 1080, codec: VideoCodec.Hevc, bitrate: 4_500_000 },
|
||||||
{ resolution: 1080, codec: VideoCodec.H264, bitrate: 8_000_000, codecString: 'avc1.640028' },
|
{ resolution: 1080, codec: VideoCodec.H264, bitrate: 8_000_000 },
|
||||||
|
{ resolution: 1440, codec: VideoCodec.Av1, bitrate: 7_000_000 },
|
||||||
|
{ resolution: 1440, codec: VideoCodec.Hevc, bitrate: 8_000_000 },
|
||||||
|
{ resolution: 1440, codec: VideoCodec.H264, bitrate: 14_000_000 },
|
||||||
|
{ resolution: 2160, codec: VideoCodec.Av1, bitrate: 12_000_000 },
|
||||||
|
{ resolution: 2160, codec: VideoCodec.Hevc, bitrate: 14_000_000 },
|
||||||
|
{ resolution: 2160, codec: VideoCodec.H264, bitrate: 25_000_000 },
|
||||||
];
|
];
|
||||||
export const HLS_VERSION = 7;
|
export const HLS_VERSION = 7;
|
||||||
|
|
||||||
|
export type CodecLevel = { maxFrame: number; maxRate: number; token: string };
|
||||||
|
|
||||||
|
// H.264 High profile: token is the hex level_idc.
|
||||||
|
export const H264_LEVELS: CodecLevel[] = [
|
||||||
|
{ maxFrame: 1620, maxRate: 40_500, token: '1e' }, // 3.0
|
||||||
|
{ maxFrame: 3600, maxRate: 108_000, token: '1f' }, // 3.1
|
||||||
|
{ maxFrame: 5120, maxRate: 216_000, token: '20' }, // 3.2
|
||||||
|
{ maxFrame: 8192, maxRate: 245_760, token: '28' }, // 4.0
|
||||||
|
{ maxFrame: 8704, maxRate: 522_240, token: '2a' }, // 4.2
|
||||||
|
{ maxFrame: 22_080, maxRate: 589_824, token: '32' }, // 5.0
|
||||||
|
{ maxFrame: 36_864, maxRate: 983_040, token: '33' }, // 5.1
|
||||||
|
{ maxFrame: 36_864, maxRate: 2_073_600, token: '34' }, // 5.2
|
||||||
|
{ maxFrame: 139_264, maxRate: 4_177_920, token: '3c' }, // 6.0
|
||||||
|
{ maxFrame: 139_264, maxRate: 8_355_840, token: '3d' }, // 6.1
|
||||||
|
{ maxFrame: 139_264, maxRate: 16_711_680, token: '3e' }, // 6.2
|
||||||
|
];
|
||||||
|
|
||||||
|
// HEVC Main profile, Main tier: token is `L` + level_idc (level × 30).
|
||||||
|
export const HEVC_LEVELS: CodecLevel[] = [
|
||||||
|
{ maxFrame: 552_960, maxRate: 16_588_800, token: 'L90' }, // 3.0
|
||||||
|
{ maxFrame: 983_040, maxRate: 33_177_600, token: 'L93' }, // 3.1
|
||||||
|
{ maxFrame: 2_228_224, maxRate: 66_846_720, token: 'L120' }, // 4.0
|
||||||
|
{ maxFrame: 2_228_224, maxRate: 133_693_440, token: 'L123' }, // 4.1
|
||||||
|
{ maxFrame: 8_912_896, maxRate: 267_386_880, token: 'L150' }, // 5.0
|
||||||
|
{ maxFrame: 8_912_896, maxRate: 534_773_760, token: 'L153' }, // 5.1
|
||||||
|
{ maxFrame: 8_912_896, maxRate: 1_069_547_520, token: 'L156' }, // 5.2
|
||||||
|
{ maxFrame: 35_651_584, maxRate: 1_069_547_520, token: 'L180' }, // 6.0
|
||||||
|
{ maxFrame: 35_651_584, maxRate: 2_139_095_040, token: 'L183' }, // 6.1
|
||||||
|
{ maxFrame: 35_651_584, maxRate: 4_278_190_080, token: 'L186' }, // 6.2
|
||||||
|
];
|
||||||
|
|
||||||
|
// AV1 Main profile (0), Main tier (M): token is the two-digit seq_level_idx + `M`.
|
||||||
|
export const AV1_LEVELS: CodecLevel[] = [
|
||||||
|
{ maxFrame: 665_856, maxRate: 19_975_168, token: '04M' }, // 3.0
|
||||||
|
{ maxFrame: 1_065_024, maxRate: 31_950_336, token: '05M' }, // 3.1
|
||||||
|
{ maxFrame: 2_359_296, maxRate: 70_778_880, token: '08M' }, // 4.0
|
||||||
|
{ maxFrame: 2_359_296, maxRate: 141_557_760, token: '09M' }, // 4.1
|
||||||
|
{ maxFrame: 8_912_896, maxRate: 267_386_880, token: '12M' }, // 5.0
|
||||||
|
{ maxFrame: 8_912_896, maxRate: 534_773_760, token: '13M' }, // 5.1
|
||||||
|
{ maxFrame: 8_912_896, maxRate: 1_069_547_520, token: '14M' }, // 5.2
|
||||||
|
{ maxFrame: 35_651_584, maxRate: 1_069_547_520, token: '16M' }, // 6.0
|
||||||
|
{ maxFrame: 35_651_584, maxRate: 2_139_095_040, token: '17M' }, // 6.1
|
||||||
|
{ maxFrame: 35_651_584, maxRate: 4_278_190_080, token: '18M' }, // 6.2
|
||||||
|
];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
AudioCodecSchema,
|
AudioCodecSchema,
|
||||||
ColorspaceSchema,
|
ColorspaceSchema,
|
||||||
CQModeSchema,
|
CQModeSchema,
|
||||||
|
HlsVideoResolutionSchema,
|
||||||
ImageFormatSchema,
|
ImageFormatSchema,
|
||||||
LogLevelSchema,
|
LogLevelSchema,
|
||||||
OAuthTokenEndpointAuthMethodSchema,
|
OAuthTokenEndpointAuthMethodSchema,
|
||||||
@@ -115,6 +116,8 @@ const SystemConfigFFmpegSchema = z
|
|||||||
realtime: z
|
realtime: z
|
||||||
.object({
|
.object({
|
||||||
enabled: configBool.describe('Enable real-time HLS transcoding (alpha)'),
|
enabled: configBool.describe('Enable real-time HLS transcoding (alpha)'),
|
||||||
|
videoCodecs: z.array(VideoCodecSchema).describe('Video codecs to use for real-time HLS transcoding'),
|
||||||
|
resolutions: z.array(HlsVideoResolutionSchema).describe('Resolutions to use for real-time HLS transcoding'),
|
||||||
})
|
})
|
||||||
.meta({ id: 'SystemConfigFFmpegRealtimeDto' }),
|
.meta({ id: 'SystemConfigFFmpegRealtimeDto' }),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ const CastUpdateSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.meta({ id: 'CastUpdate' });
|
.meta({ id: 'CastUpdate' });
|
||||||
|
|
||||||
|
const RecentlyAddedUpdateSchema = z
|
||||||
|
.object({
|
||||||
|
sidebarWeb: z.boolean().optional().describe('Whether the recently added page appears in the web sidebar'),
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.meta({ id: 'RecentlyAddedUpdate' });
|
||||||
|
|
||||||
const UserPreferencesUpdateSchema = z
|
const UserPreferencesUpdateSchema = z
|
||||||
.object({
|
.object({
|
||||||
albums: AlbumsUpdateSchema,
|
albums: AlbumsUpdateSchema,
|
||||||
@@ -112,6 +119,7 @@ const UserPreferencesUpdateSchema = z
|
|||||||
ratings: RatingsUpdateSchema,
|
ratings: RatingsUpdateSchema,
|
||||||
sharedLinks: SharedLinksUpdateSchema,
|
sharedLinks: SharedLinksUpdateSchema,
|
||||||
tags: TagsUpdateSchema,
|
tags: TagsUpdateSchema,
|
||||||
|
recentlyAdded: RecentlyAddedUpdateSchema,
|
||||||
})
|
})
|
||||||
.meta({ id: 'UserPreferencesUpdateDto' });
|
.meta({ id: 'UserPreferencesUpdateDto' });
|
||||||
|
|
||||||
@@ -191,6 +199,12 @@ const CastResponseSchema = z
|
|||||||
})
|
})
|
||||||
.meta({ id: 'CastResponse' });
|
.meta({ id: 'CastResponse' });
|
||||||
|
|
||||||
|
const RecentlyAddedResponseSchema = z
|
||||||
|
.object({
|
||||||
|
sidebarWeb: z.boolean().describe('Whether the recently added page appears in the web sidebar'),
|
||||||
|
})
|
||||||
|
.meta({ id: 'RecentlyAddedResponse' });
|
||||||
|
|
||||||
const UserPreferencesResponseSchema = z
|
const UserPreferencesResponseSchema = z
|
||||||
.object({
|
.object({
|
||||||
albums: AlbumsResponseSchema,
|
albums: AlbumsResponseSchema,
|
||||||
@@ -204,6 +218,7 @@ const UserPreferencesResponseSchema = z
|
|||||||
download: DownloadResponseSchema,
|
download: DownloadResponseSchema,
|
||||||
purchase: PurchaseResponseSchema,
|
purchase: PurchaseResponseSchema,
|
||||||
cast: CastResponseSchema,
|
cast: CastResponseSchema,
|
||||||
|
recentlyAdded: RecentlyAddedResponseSchema,
|
||||||
})
|
})
|
||||||
.meta({ id: 'UserPreferencesResponseDto' });
|
.meta({ id: 'UserPreferencesResponseDto' });
|
||||||
|
|
||||||
|
|||||||
@@ -529,6 +529,19 @@ export enum CQMode {
|
|||||||
|
|
||||||
export const CQModeSchema = z.enum(CQMode).describe('CQ mode').meta({ id: 'CQMode' });
|
export const CQModeSchema = z.enum(CQMode).describe('CQ mode').meta({ id: 'CQMode' });
|
||||||
|
|
||||||
|
export enum HlsVideoResolution {
|
||||||
|
p480 = 480,
|
||||||
|
p720 = 720,
|
||||||
|
p1080 = 1080,
|
||||||
|
p1440 = 1440,
|
||||||
|
p2160 = 2160,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HlsVideoResolutionSchema = z
|
||||||
|
.enum(HlsVideoResolution)
|
||||||
|
.describe('HLS video resolution')
|
||||||
|
.meta({ id: 'HlsVideoResolution', type: 'integer' });
|
||||||
|
|
||||||
export enum Colorspace {
|
export enum Colorspace {
|
||||||
Srgb = 'srgb',
|
Srgb = 'srgb',
|
||||||
P3 = 'p3',
|
P3 = 'p3',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||||
import { TranscodeHardwareAcceleration } from 'src/enum';
|
import { HlsVideoResolution, VideoCodec } from 'src/enum';
|
||||||
import { HlsService } from 'src/services/hls.service';
|
import { HlsService } from 'src/services/hls.service';
|
||||||
import { eiffelTower, train, waterfall } from 'test/fixtures/media.stub';
|
import { eiffelTower, train, waterfall } from 'test/fixtures/media.stub';
|
||||||
import { factory } from 'test/small.factory';
|
import { factory } from 'test/small.factory';
|
||||||
@@ -96,67 +96,79 @@ seg_10.m4s
|
|||||||
|
|
||||||
const sessionId = '00000000-0000-0000-0000-000000000000';
|
const sessionId = '00000000-0000-0000-0000-000000000000';
|
||||||
|
|
||||||
const eiffelExpectedMasterDisabled = `#EXTM3U
|
const eiffelExpectedMasterAv1 = `#EXTM3U
|
||||||
#EXT-X-VERSION:7
|
#EXT-X-VERSION:7
|
||||||
#EXT-X-INDEPENDENT-SEGMENTS
|
#EXT-X-INDEPENDENT-SEGMENTS
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=480x852,CODECS="av01.0.04M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=1350000,RESOLUTION=480x852,CODECS="av01.0.04M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/0/playlist.m3u8
|
${sessionId}/0/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=1620000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/1/playlist.m3u8
|
${sessionId}/1/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=480x852,CODECS="avc1.64001e,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=3375000,RESOLUTION=480x852,CODECS="avc1.64001e,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/2/playlist.m3u8
|
${sessionId}/2/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=720x1280,CODECS="av01.0.08M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=2700000,RESOLUTION=720x1280,CODECS="av01.0.05M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/3/playlist.m3u8
|
${sessionId}/3/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=720x1280,CODECS="hvc1.1.6.L93.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=3375000,RESOLUTION=720x1280,CODECS="hvc1.1.6.L93.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/4/playlist.m3u8
|
${sessionId}/4/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=6750000,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/5/playlist.m3u8
|
${sessionId}/5/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1080x1920,CODECS="av01.0.09M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=5400000,RESOLUTION=1080x1920,CODECS="av01.0.08M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/6/playlist.m3u8
|
${sessionId}/6/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=4500000,RESOLUTION=1080x1920,CODECS="hvc1.1.6.L120.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=6075000,RESOLUTION=1080x1920,CODECS="hvc1.1.6.L120.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/7/playlist.m3u8
|
${sessionId}/7/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=10800000,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/8/playlist.m3u8
|
${sessionId}/8/playlist.m3u8
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const eiffelExpectedMasterRkmpp = `#EXTM3U
|
const eiffelExpectedMasterNoAv1 = `#EXTM3U
|
||||||
#EXT-X-VERSION:7
|
#EXT-X-VERSION:7
|
||||||
#EXT-X-INDEPENDENT-SEGMENTS
|
#EXT-X-INDEPENDENT-SEGMENTS
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=1620000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/1/playlist.m3u8
|
${sessionId}/1/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=480x852,CODECS="avc1.64001e,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=3375000,RESOLUTION=480x852,CODECS="avc1.64001e,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/2/playlist.m3u8
|
${sessionId}/2/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=720x1280,CODECS="hvc1.1.6.L93.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=3375000,RESOLUTION=720x1280,CODECS="hvc1.1.6.L93.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/4/playlist.m3u8
|
${sessionId}/4/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=6750000,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/5/playlist.m3u8
|
${sessionId}/5/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=4500000,RESOLUTION=1080x1920,CODECS="hvc1.1.6.L120.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=6075000,RESOLUTION=1080x1920,CODECS="hvc1.1.6.L120.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/7/playlist.m3u8
|
${sessionId}/7/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
#EXT-X-STREAM-INF:BANDWIDTH=10800000,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910
|
||||||
${sessionId}/8/playlist.m3u8
|
${sessionId}/8/playlist.m3u8
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const waterfallExpectedMasterDisabled = `#EXTM3U
|
const waterfallExpectedMasterAv1 = `#EXTM3U
|
||||||
#EXT-X-VERSION:7
|
#EXT-X-VERSION:7
|
||||||
#EXT-X-INDEPENDENT-SEGMENTS
|
#EXT-X-INDEPENDENT-SEGMENTS
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=480x852,CODECS="av01.0.04M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
#EXT-X-STREAM-INF:BANDWIDTH=1350000,RESOLUTION=480x852,CODECS="av01.0.04M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
${sessionId}/0/playlist.m3u8
|
${sessionId}/0/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
#EXT-X-STREAM-INF:BANDWIDTH=1620000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
${sessionId}/1/playlist.m3u8
|
${sessionId}/1/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=480x852,CODECS="avc1.64001e,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
#EXT-X-STREAM-INF:BANDWIDTH=3375000,RESOLUTION=480x852,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
${sessionId}/2/playlist.m3u8
|
${sessionId}/2/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=720x1280,CODECS="av01.0.08M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
#EXT-X-STREAM-INF:BANDWIDTH=2700000,RESOLUTION=720x1280,CODECS="av01.0.05M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
${sessionId}/3/playlist.m3u8
|
${sessionId}/3/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=720x1280,CODECS="hvc1.1.6.L93.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
#EXT-X-STREAM-INF:BANDWIDTH=3375000,RESOLUTION=720x1280,CODECS="hvc1.1.6.L93.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
${sessionId}/4/playlist.m3u8
|
${sessionId}/4/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
#EXT-X-STREAM-INF:BANDWIDTH=6750000,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
${sessionId}/5/playlist.m3u8
|
${sessionId}/5/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1080x1920,CODECS="av01.0.09M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
#EXT-X-STREAM-INF:BANDWIDTH=5400000,RESOLUTION=1080x1920,CODECS="av01.0.08M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
${sessionId}/6/playlist.m3u8
|
${sessionId}/6/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=4500000,RESOLUTION=1080x1920,CODECS="hvc1.1.6.L120.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
#EXT-X-STREAM-INF:BANDWIDTH=6075000,RESOLUTION=1080x1920,CODECS="hvc1.1.6.L120.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
${sessionId}/7/playlist.m3u8
|
${sessionId}/7/playlist.m3u8
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
#EXT-X-STREAM-INF:BANDWIDTH=10800000,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
${sessionId}/8/playlist.m3u8
|
${sessionId}/8/playlist.m3u8
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=9450000,RESOLUTION=1440x2560,CODECS="av01.0.12M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
|
${sessionId}/9/playlist.m3u8
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=10800000,RESOLUTION=1440x2560,CODECS="hvc1.1.6.L150.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
|
${sessionId}/10/playlist.m3u8
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=18900000,RESOLUTION=1440x2560,CODECS="avc1.640032,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
|
${sessionId}/11/playlist.m3u8
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=16200000,RESOLUTION=2160x3840,CODECS="av01.0.12M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
|
${sessionId}/12/playlist.m3u8
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=18900000,RESOLUTION=2160x3840,CODECS="hvc1.1.6.L150.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
|
${sessionId}/13/playlist.m3u8
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=33750000,RESOLUTION=2160x3840,CODECS="avc1.640033,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830
|
||||||
|
${sessionId}/14/playlist.m3u8
|
||||||
`;
|
`;
|
||||||
|
|
||||||
describe(HlsService.name, () => {
|
describe(HlsService.name, () => {
|
||||||
@@ -171,9 +183,24 @@ describe(HlsService.name, () => {
|
|||||||
const auth = factory.auth();
|
const auth = factory.auth();
|
||||||
const assetId = 'asset-1';
|
const assetId = 'asset-1';
|
||||||
|
|
||||||
const setup = (asset: typeof eiffelTower | typeof waterfall, accel: TranscodeHardwareAcceleration) => {
|
const allCodecs = [VideoCodec.Av1, VideoCodec.Hevc, VideoCodec.H264];
|
||||||
|
const allResolutions = [
|
||||||
|
HlsVideoResolution.p480,
|
||||||
|
HlsVideoResolution.p720,
|
||||||
|
HlsVideoResolution.p1080,
|
||||||
|
HlsVideoResolution.p1440,
|
||||||
|
HlsVideoResolution.p2160,
|
||||||
|
];
|
||||||
|
|
||||||
|
const setup = (
|
||||||
|
asset: typeof eiffelTower | typeof waterfall,
|
||||||
|
videoCodecs?: VideoCodec[],
|
||||||
|
resolutions?: HlsVideoResolution[],
|
||||||
|
) => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId]));
|
||||||
mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { realtime: { enabled: true }, accel } });
|
mocks.systemMetadata.get.mockResolvedValue({
|
||||||
|
ffmpeg: { realtime: { enabled: true, videoCodecs, resolutions } },
|
||||||
|
});
|
||||||
mocks.videoStream.getForMainPlaylist.mockResolvedValue(asset);
|
mocks.videoStream.getForMainPlaylist.mockResolvedValue(asset);
|
||||||
mocks.crypto.randomUUID.mockReturnValue(sessionId);
|
mocks.crypto.randomUUID.mockReturnValue(sessionId);
|
||||||
mocks.websocket.serverSend.mockImplementation((event, ...rest) => {
|
mocks.websocket.serverSend.mockImplementation((event, ...rest) => {
|
||||||
@@ -184,19 +211,19 @@ describe(HlsService.name, () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
it('returns main playlist for eiffel-tower (1080p portrait, no acceleration)', async () => {
|
it('offers AV1, HEVC, and H.264 when AV1 is configured and the accelerator supports it', async () => {
|
||||||
setup(eiffelTower, TranscodeHardwareAcceleration.Disabled);
|
setup(eiffelTower, allCodecs);
|
||||||
await expect(sut.getMainPlaylist(auth, assetId)).resolves.toBe(eiffelExpectedMasterDisabled);
|
await expect(sut.getMainPlaylist(auth, assetId)).resolves.toBe(eiffelExpectedMasterAv1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns main playlist for eiffel-tower with RKMPP (no AV1 variants)', async () => {
|
it('omits AV1 when it is not in the configured codecs', async () => {
|
||||||
setup(eiffelTower, TranscodeHardwareAcceleration.Rkmpp);
|
setup(eiffelTower);
|
||||||
await expect(sut.getMainPlaylist(auth, assetId)).resolves.toBe(eiffelExpectedMasterRkmpp);
|
await expect(sut.getMainPlaylist(auth, assetId)).resolves.toBe(eiffelExpectedMasterNoAv1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns main playlist for waterfall (4K landscape) with no acceleration', async () => {
|
it('offers every resolution up to the source and derives 4K codec levels (waterfall, 4K, 29.83fps)', async () => {
|
||||||
setup(waterfall, TranscodeHardwareAcceleration.Disabled);
|
setup(waterfall, allCodecs, allResolutions);
|
||||||
await expect(sut.getMainPlaylist(auth, assetId)).resolves.toBe(waterfallExpectedMasterDisabled);
|
await expect(sut.getMainPlaylist(auth, assetId)).resolves.toBe(waterfallExpectedMasterAv1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws BadRequestException when realtime transcoding is disabled', async () => {
|
it('throws BadRequestException when realtime transcoding is disabled', async () => {
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { constants } from 'node:fs';
|
import { constants } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import {
|
import { HLS_SEGMENT_DURATION, HLS_SEGMENT_FILENAME_REGEX, HLS_VARIANTS, HLS_VERSION } from 'src/constants';
|
||||||
HLS_SEGMENT_DURATION,
|
|
||||||
HLS_SEGMENT_FILENAME_REGEX,
|
|
||||||
HLS_VARIANTS,
|
|
||||||
HLS_VERSION,
|
|
||||||
SUPPORTED_HWA_CODECS,
|
|
||||||
} from 'src/constants';
|
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
@@ -18,7 +12,7 @@ import { BaseService } from 'src/services/base.service';
|
|||||||
import { VideoPacketInfo, VideoStreamInfo } from 'src/types';
|
import { VideoPacketInfo, VideoStreamInfo } from 'src/types';
|
||||||
import { PendingEvents } from 'src/utils/event';
|
import { PendingEvents } from 'src/utils/event';
|
||||||
import { ImmichFileResponse } from 'src/utils/file';
|
import { ImmichFileResponse } from 'src/utils/file';
|
||||||
import { getOutputSize } from 'src/utils/media';
|
import { getCodecString, getOutputSize } from 'src/utils/media';
|
||||||
|
|
||||||
type AssetWithStreamInfo = { videoStream: VideoStreamInfo & { timeBase: number }; packets: VideoPacketInfo };
|
type AssetWithStreamInfo = { videoStream: VideoStreamInfo & { timeBase: number }; packets: VideoPacketInfo };
|
||||||
type Segmentation = { fps: number; framesPerSegment: number; segmentCount: number; segmentDuration: number };
|
type Segmentation = { fps: number; framesPerSegment: number; segmentCount: number; segmentDuration: number };
|
||||||
@@ -131,18 +125,21 @@ export class HlsService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private generateMainPlaylist(sessionId: string, ffmpeg: SystemConfigFFmpegDto, asset: AssetWithStreamInfo) {
|
private generateMainPlaylist(sessionId: string, ffmpeg: SystemConfigFFmpegDto, asset: AssetWithStreamInfo) {
|
||||||
const fps = ((asset.packets.packetCount * asset.videoStream.timeBase) / asset.packets.totalDuration).toFixed(3);
|
const fps = (asset.packets.packetCount * asset.videoStream.timeBase) / asset.packets.totalDuration;
|
||||||
|
const roundedFps = fps.toFixed(3);
|
||||||
const sourceResolution = Math.min(asset.videoStream.height, asset.videoStream.width);
|
const sourceResolution = Math.min(asset.videoStream.height, asset.videoStream.width);
|
||||||
const targetResolution = Math.max(sourceResolution, HLS_VARIANTS[0].resolution);
|
const targetResolution = Math.max(sourceResolution, HLS_VARIANTS[0].resolution);
|
||||||
const lines = ['#EXTM3U', `#EXT-X-VERSION:${HLS_VERSION}`, '#EXT-X-INDEPENDENT-SEGMENTS'];
|
const lines = ['#EXTM3U', `#EXT-X-VERSION:${HLS_VERSION}`, '#EXT-X-INDEPENDENT-SEGMENTS'];
|
||||||
|
const { videoCodecs, resolutions } = ffmpeg.realtime;
|
||||||
for (let i = 0; i < HLS_VARIANTS.length; i++) {
|
for (let i = 0; i < HLS_VARIANTS.length; i++) {
|
||||||
const { resolution, bitrate, codec, codecString } = HLS_VARIANTS[i];
|
const { resolution, bitrate, codec } = HLS_VARIANTS[i];
|
||||||
if (resolution > targetResolution || !SUPPORTED_HWA_CODECS[ffmpeg.accel].includes(codec)) {
|
if (resolution > targetResolution || !videoCodecs.includes(codec) || !resolutions.includes(resolution)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { width, height } = getOutputSize(asset.videoStream, resolution);
|
const { width, height } = getOutputSize(asset.videoStream, resolution);
|
||||||
|
const codecString = getCodecString(codec, width, height, fps);
|
||||||
lines.push(
|
lines.push(
|
||||||
`#EXT-X-STREAM-INF:BANDWIDTH=${bitrate},RESOLUTION=${width}x${height},CODECS="${codecString},mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=${fps}`,
|
`#EXT-X-STREAM-INF:BANDWIDTH=${Math.round(bitrate * 1.35)},RESOLUTION=${width}x${height},CODECS="${codecString},mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=${roundedFps}`,
|
||||||
`${sessionId}/${i}/playlist.m3u8`,
|
`${sessionId}/${i}/playlist.m3u8`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
AudioCodec,
|
AudioCodec,
|
||||||
Colorspace,
|
Colorspace,
|
||||||
CQMode,
|
CQMode,
|
||||||
|
HlsVideoResolution,
|
||||||
ImageFormat,
|
ImageFormat,
|
||||||
LogLevel,
|
LogLevel,
|
||||||
OAuthTokenEndpointAuthMethod,
|
OAuthTokenEndpointAuthMethod,
|
||||||
@@ -76,6 +77,8 @@ const updatedConfig = Object.freeze<SystemConfig>({
|
|||||||
tonemap: ToneMapping.Hable,
|
tonemap: ToneMapping.Hable,
|
||||||
realtime: {
|
realtime: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
videoCodecs: [VideoCodec.H264, VideoCodec.Hevc],
|
||||||
|
resolutions: [HlsVideoResolution.p480, HlsVideoResolution.p720, HlsVideoResolution.p1080],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
integrityChecks: {
|
integrityChecks: {
|
||||||
|
|||||||
@@ -381,6 +381,8 @@ describe(TranscodingService.name, () => {
|
|||||||
'50',
|
'50',
|
||||||
'-keyint_min',
|
'-keyint_min',
|
||||||
'50',
|
'50',
|
||||||
|
'-ac',
|
||||||
|
'2',
|
||||||
'-copyts',
|
'-copyts',
|
||||||
'-r',
|
'-r',
|
||||||
'50130000/2012441',
|
'50130000/2012441',
|
||||||
|
|||||||
@@ -619,6 +619,9 @@ export type UserPreferences = {
|
|||||||
cast: {
|
cast: {
|
||||||
gCastEnabled: boolean;
|
gCastEnabled: boolean;
|
||||||
};
|
};
|
||||||
|
recentlyAdded: {
|
||||||
|
sidebarWeb: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserMetadataItem<T extends keyof UserMetadata = UserMetadataKey> = {
|
export type UserMetadataItem<T extends keyof UserMetadata = UserMetadataKey> = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AUDIO_ENCODER, SUPPORTED_HWA_CODECS } from 'src/constants';
|
import { AUDIO_ENCODER, AV1_LEVELS, CodecLevel, H264_LEVELS, HEVC_LEVELS, SUPPORTED_HWA_CODECS } from 'src/constants';
|
||||||
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
|
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
|
||||||
import {
|
import {
|
||||||
ColorMatrix,
|
ColorMatrix,
|
||||||
@@ -36,6 +36,29 @@ export const getOutputSize = (videoStream: VideoStreamInfo, targetRes: number) =
|
|||||||
return isVideoVertical(videoStream) ? { width: targetRes, height: larger } : { width: larger, height: targetRes };
|
return isVideoVertical(videoStream) ? { width: targetRes, height: larger } : { width: larger, height: targetRes };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pickLevel = (levels: CodecLevel[], frame: number, rate: number) =>
|
||||||
|
levels.find((level) => frame <= level.maxFrame && rate <= level.maxRate) ?? levels.at(-1)!;
|
||||||
|
|
||||||
|
export const getCodecString = (codec: VideoCodec, width: number, height: number, fps: number): string => {
|
||||||
|
switch (codec) {
|
||||||
|
case VideoCodec.H264: {
|
||||||
|
const macroblocks = Math.ceil(width / 16) * Math.ceil(height / 16);
|
||||||
|
return `avc1.6400${pickLevel(H264_LEVELS, macroblocks, macroblocks * fps).token}`;
|
||||||
|
}
|
||||||
|
case VideoCodec.Hevc: {
|
||||||
|
const samples = width * height;
|
||||||
|
return `hvc1.1.6.${pickLevel(HEVC_LEVELS, samples, samples * fps).token}.B0`;
|
||||||
|
}
|
||||||
|
case VideoCodec.Av1: {
|
||||||
|
const samples = width * height;
|
||||||
|
return `av01.0.${pickLevel(AV1_LEVELS, samples, samples * fps).token}.08`;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`Codec '${codec}' does not support HLS codec strings`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export class BaseConfig implements VideoCodecSWConfig {
|
export class BaseConfig implements VideoCodecSWConfig {
|
||||||
readonly presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast'];
|
readonly presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast'];
|
||||||
protected constructor(
|
protected constructor(
|
||||||
@@ -200,6 +223,11 @@ export class BaseConfig implements VideoCodecSWConfig {
|
|||||||
const options = ['-c:v', videoCodec, '-c:a', audioCodec, '-map', `0:${videoStream.index}`, '-map_metadata', '-1'];
|
const options = ['-c:v', videoCodec, '-c:a', audioCodec, '-map', `0:${videoStream.index}`, '-map_metadata', '-1'];
|
||||||
if (audioStream) {
|
if (audioStream) {
|
||||||
options.push('-map', `0:${audioStream.index}`);
|
options.push('-map', `0:${audioStream.index}`);
|
||||||
|
// If there are more than 2 channels sometimes the channel config is broken when re-encoded
|
||||||
|
// TODO: Store the number of channels in the db and then set it during the transcoding: -channel_layout 5.1
|
||||||
|
if ([TranscodeTarget.All, TranscodeTarget.Audio].includes(target)) {
|
||||||
|
options.push('-ac', '2');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.getBFrames() > -1) {
|
if (this.getBFrames() > -1) {
|
||||||
options.push('-bf', `${this.getBFrames()}`);
|
options.push('-bf', `${this.getBFrames()}`);
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ const getDefaultPreferences = (): UserPreferences => {
|
|||||||
cast: {
|
cast: {
|
||||||
gCastEnabled: false,
|
gCastEnabled: false,
|
||||||
},
|
},
|
||||||
|
recentlyAdded: {
|
||||||
|
sidebarWeb: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -107,6 +107,6 @@
|
|||||||
<Input bind:value={() => getValue<string>(), setValue} />
|
<Input bind:value={() => getValue<string>(), setValue} />
|
||||||
</Field>
|
</Field>
|
||||||
{:else}
|
{:else}
|
||||||
<Text>Unknown schema</Text>
|
<Text>{$t('unknown_schema')}</Text>
|
||||||
<CodeBlock code={JSON.stringify(schema, null, 2)} />
|
<CodeBlock code={JSON.stringify(schema, null, 2)} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||||
import { castManager } from '$lib/managers/cast-manager.svelte';
|
import { castManager } from '$lib/managers/cast-manager.svelte';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { mediaCapabilitiesManager } from '$lib/managers/media-capabilities-manager.svelte';
|
import { mediaCapabilitiesManager } from '$lib/managers/media-capabilities-manager.svelte';
|
||||||
import { autoPlayVideo, lang, loopVideo as loopVideoPreference } from '$lib/stores/preferences.store';
|
import { autoPlayVideo, lang, loopVideo as loopVideoPreference } from '$lib/stores/preferences.store';
|
||||||
import { getAssetHlsSessionUrl, getAssetHlsUrl, getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
|
import { getAssetHlsSessionUrl, getAssetHlsUrl, getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
|
||||||
@@ -150,6 +151,15 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
useMediaCapabilities: false,
|
useMediaCapabilities: false,
|
||||||
|
xhrSetup: (xhr: XMLHttpRequest, url: string) => {
|
||||||
|
const authenticatedUrl = new URL(url, globalThis.location.origin);
|
||||||
|
for (const [key, value] of Object.entries(authManager.params)) {
|
||||||
|
if (value) {
|
||||||
|
authenticatedUrl.searchParams.set(key, value as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhr.open('GET', authenticatedUrl.toString());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const releaseSession = () => {
|
const releaseSession = () => {
|
||||||
|
|||||||
@@ -37,18 +37,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#await valuePromise}
|
{#await valuePromise}
|
||||||
<div class="relative mx-auto font-mono text-2xl font-medium">
|
<div class="relative mx-auto font-mono text-2xl font-medium" aria-label="0">
|
||||||
<span class="shimmer-text text-gray-300 dark:text-gray-600">{zeros()}</span>
|
<span class="shimmer-text text-gray-300 dark:text-gray-600">{zeros()}</span>
|
||||||
</div>
|
</div>
|
||||||
{:then data}
|
{:then data}
|
||||||
<div class="relative mx-auto font-mono text-2xl font-medium">
|
<div class="relative mx-auto font-mono text-2xl font-medium" aria-label="{data.value} {data.unit ?? ''}">
|
||||||
<span class="text-gray-300 dark:text-gray-600">{zeros(data)}</span><span>{data.value}</span>
|
<span class="text-gray-300 dark:text-gray-600">{zeros(data)}</span><span>{data.value}</span>
|
||||||
{#if data.unit}
|
{#if data.unit}
|
||||||
<code class="font-mono text-base font-normal">{data.unit}</code>
|
<code class="font-mono text-base font-normal">{data.unit}</code>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:catch _}
|
{:catch _}
|
||||||
<div class="relative mx-auto font-mono text-2xl font-medium">
|
<div class="relative mx-auto font-mono text-2xl font-medium" aria-label="0">
|
||||||
<span class="text-gray-300 dark:text-gray-600">{zeros()}</span>
|
<span class="text-gray-300 dark:text-gray-600">{zeros()}</span>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@@ -101,7 +101,9 @@
|
|||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
|
<div class="pb-2">
|
||||||
{@render descriptionSnippet?.()}
|
{@render descriptionSnippet?.()}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if inputType !== SettingInputFieldType.PASSWORD}
|
{#if inputType !== SettingInputFieldType.PASSWORD}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
mdiToolboxOutline,
|
mdiToolboxOutline,
|
||||||
mdiTrashCan,
|
mdiTrashCan,
|
||||||
mdiTrashCanOutline,
|
mdiTrashCanOutline,
|
||||||
|
mdiUploadOutline,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
@@ -83,6 +84,14 @@
|
|||||||
<NavbarItem title={$t('tags')} href={Route.tags()} icon={{ icon: mdiTagMultipleOutline, flipped: true }} />
|
<NavbarItem title={$t('tags')} href={Route.tags()} icon={{ icon: mdiTagMultipleOutline, flipped: true }} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if authManager.preferences.recentlyAdded.sidebarWeb}
|
||||||
|
<NavbarItem
|
||||||
|
title={$t('recently_added')}
|
||||||
|
href={Route.recentlyAdded()}
|
||||||
|
icon={{ icon: mdiUploadOutline, flipped: true }}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if authManager.preferences.folders.enabled && authManager.preferences.folders.sidebarWeb}
|
{#if authManager.preferences.folders.enabled && authManager.preferences.folders.sidebarWeb}
|
||||||
<NavbarItem title={$t('folders')} href={Route.folders()} icon={{ icon: mdiFolderOutline, flipped: true }} />
|
<NavbarItem title={$t('folders')} href={Route.folders()} icon={{ icon: mdiFolderOutline, flipped: true }} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -58,5 +58,14 @@ describe('i18n', () => {
|
|||||||
expect(getClosestAvailableLocale(['sr_Cyrl'], allLocales)).toBe('sr_Cyrl');
|
expect(getClosestAvailableLocale(['sr_Cyrl'], allLocales)).toBe('sr_Cyrl');
|
||||||
expect(getClosestAvailableLocale(['zh_Hant'], allLocales)).toBe('zh_Hant');
|
expect(getClosestAvailableLocale(['zh_Hant'], allLocales)).toBe('zh_Hant');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle language aliases', () => {
|
||||||
|
const allLocales = ['zh-Hans', 'zh-Hant'];
|
||||||
|
expect(getClosestAvailableLocale(['zh-CN'], allLocales)).toBe('zh-Hans');
|
||||||
|
expect(getClosestAvailableLocale(['zh-HK'], allLocales)).toBe('zh-Hant');
|
||||||
|
expect(getClosestAvailableLocale(['zh-MO'], allLocales)).toBe('zh-Hant');
|
||||||
|
expect(getClosestAvailableLocale(['zh-SG'], allLocales)).toBe('zh-Hans');
|
||||||
|
expect(getClosestAvailableLocale(['zh-TW'], allLocales)).toBe('zh-Hant');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
options={[
|
options={[
|
||||||
{ label: $t('role_editor'), value: AlbumUserRole.Editor },
|
{ label: $t('role_editor'), value: AlbumUserRole.Editor },
|
||||||
{ label: $t('role_viewer'), value: AlbumUserRole.Viewer },
|
{ label: $t('role_viewer'), value: AlbumUserRole.Viewer },
|
||||||
{ label: $t('owner'), value: AlbumUserRole.Owner },
|
{ label: $t('owner'), value: AlbumUserRole.Owner, disabled: true },
|
||||||
{ label: $t('remove_user'), value: 'none' },
|
{ label: $t('remove_user'), value: 'none' },
|
||||||
] as SelectOption<AlbumUserRole | 'none'>[]}
|
] as SelectOption<AlbumUserRole | 'none'>[]}
|
||||||
onChange={(value) => handleRoleSelect(user, value)}
|
onChange={(value) => handleRoleSelect(user, value)}
|
||||||
|
|||||||
@@ -134,10 +134,13 @@ const notifyAddToAlbum = ($t: MessageFormatter, albumId: string, assetIds: strin
|
|||||||
const successCount = results.filter(({ success }) => success).length;
|
const successCount = results.filter(({ success }) => success).length;
|
||||||
const duplicateCount = results.filter(({ error }) => error === 'duplicate').length;
|
const duplicateCount = results.filter(({ error }) => error === 'duplicate').length;
|
||||||
let description = $t('assets_cannot_be_added_to_album_count', { values: { count: assetIds.length } });
|
let description = $t('assets_cannot_be_added_to_album_count', { values: { count: assetIds.length } });
|
||||||
if (successCount > 0) {
|
|
||||||
description = $t('assets_added_to_album_count', { values: { count: successCount } });
|
if (duplicateCount === assetIds.length) {
|
||||||
} else if (duplicateCount > 0) {
|
|
||||||
description = $t('assets_were_part_of_album_count', { values: { count: duplicateCount } });
|
description = $t('assets_were_part_of_album_count', { values: { count: duplicateCount } });
|
||||||
|
} else if (successCount === assetIds.length) {
|
||||||
|
description = $t('assets_added_to_album_count', { values: { count: successCount } });
|
||||||
|
} else if (successCount > 0) {
|
||||||
|
description = $t('assets_added_to_album_partial_count', { values: { successCount, totalCount: assetIds.length } });
|
||||||
}
|
}
|
||||||
|
|
||||||
toastManager.primary(
|
toastManager.primary(
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export class GCastDestination implements ICastDestination {
|
|||||||
private currentUrl: string | null = null;
|
private currentUrl: string | null = null;
|
||||||
|
|
||||||
async initialize(): Promise<boolean> {
|
async initialize(): Promise<boolean> {
|
||||||
if (!authManager.authenticated || authManager.preferences.cast.gCastEnabled) {
|
if (!authManager.authenticated || !authManager.preferences.cast.gCastEnabled) {
|
||||||
this.isAvailable = false;
|
this.isAvailable = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ export const getFormatter = async () => {
|
|||||||
return get(t);
|
return get(t);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const aliases: Record<string, string> = {
|
||||||
|
'zh-CN': 'zh-Hans',
|
||||||
|
'zh-HK': 'zh-Hant',
|
||||||
|
'zh-MO': 'zh-Hant',
|
||||||
|
'zh-SG': 'zh-Hans',
|
||||||
|
'zh-TW': 'zh-Hant',
|
||||||
|
};
|
||||||
|
|
||||||
const modules = import.meta.glob('$i18n/*.json');
|
const modules = import.meta.glob('$i18n/*.json');
|
||||||
|
|
||||||
const fileCodes = Object.keys(modules)
|
const fileCodes = Object.keys(modules)
|
||||||
@@ -33,7 +41,9 @@ const getSubLocales = (locale: string) => {
|
|||||||
|
|
||||||
export const getClosestAvailableLocale = (locales: readonly string[], allLocales: readonly string[]) => {
|
export const getClosestAvailableLocale = (locales: readonly string[], allLocales: readonly string[]) => {
|
||||||
const allLocalesSet = new Set(allLocales.map((locale) => convertBCP47(locale)));
|
const allLocalesSet = new Set(allLocales.map((locale) => convertBCP47(locale)));
|
||||||
return locales.find((locale) => getSubLocales(locale).some((subLocale) => allLocalesSet.has(subLocale)));
|
return locales
|
||||||
|
.map((locale) => aliases[locale] ?? locale)
|
||||||
|
.find((locale) => getSubLocales(locale).some((subLocale) => allLocalesSet.has(subLocale)));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPreferredLocale = () => getClosestAvailableLocale(navigator.languages, langCodes);
|
export const getPreferredLocale = () => getClosestAvailableLocale(navigator.languages, langCodes);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
import {
|
import {
|
||||||
type AlbumResponseDto,
|
type AlbumResponseDto,
|
||||||
type AssetResponseDto,
|
type AssetResponseDto,
|
||||||
|
AssetVisibility,
|
||||||
getPerson,
|
getPerson,
|
||||||
getTagById,
|
getTagById,
|
||||||
type MetadataSearchDto,
|
type MetadataSearchDto,
|
||||||
@@ -141,8 +142,10 @@
|
|||||||
try {
|
try {
|
||||||
const { albums, assets } =
|
const { albums, assets } =
|
||||||
('query' in searchDto || 'queryAssetId' in searchDto) && smartSearchEnabled
|
('query' in searchDto || 'queryAssetId' in searchDto) && smartSearchEnabled
|
||||||
? await searchSmart({ smartSearchDto: { ...searchDto, language: $lang } })
|
? await searchSmart({
|
||||||
: await searchAssets({ metadataSearchDto: searchDto });
|
smartSearchDto: { visibility: AssetVisibility.Timeline, ...searchDto, language: $lang },
|
||||||
|
})
|
||||||
|
: await searchAssets({ metadataSearchDto: { visibility: AssetVisibility.Timeline, ...searchDto } });
|
||||||
|
|
||||||
searchResultAlbums.push(...albums.items);
|
searchResultAlbums.push(...albums.items);
|
||||||
searchResultAssets.push(...assets.items);
|
searchResultAssets.push(...assets.items);
|
||||||
|
|||||||
@@ -38,6 +38,9 @@
|
|||||||
// Cast
|
// Cast
|
||||||
let gCastEnabled = $state(authManager.preferences.cast?.gCastEnabled ?? false);
|
let gCastEnabled = $state(authManager.preferences.cast?.gCastEnabled ?? false);
|
||||||
|
|
||||||
|
// Recently added
|
||||||
|
let recentlyAddedSidebar = $state(authManager.preferences.recentlyAdded?.sidebarWeb ?? false);
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await updateMyPreferences({
|
const response = await updateMyPreferences({
|
||||||
@@ -50,6 +53,7 @@
|
|||||||
sharedLinks: { enabled: sharedLinksEnabled, sidebarWeb: sharedLinkSidebar },
|
sharedLinks: { enabled: sharedLinksEnabled, sidebarWeb: sharedLinkSidebar },
|
||||||
tags: { enabled: tagsEnabled, sidebarWeb: tagsSidebar },
|
tags: { enabled: tagsEnabled, sidebarWeb: tagsSidebar },
|
||||||
cast: { gCastEnabled },
|
cast: { gCastEnabled },
|
||||||
|
recentlyAdded: { sidebarWeb: recentlyAddedSidebar },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -170,6 +174,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
|
||||||
|
<SettingAccordion key="recentlyAdded" title={$t('recently_added')} subtitle={$t('recently_added_description')}>
|
||||||
|
<div class="mt-4 flex flex-col gap-4 sm:ms-4">
|
||||||
|
<Field label={$t('sidebar')} description={$t('sidebar_display_description')}>
|
||||||
|
<Switch bind:checked={recentlyAddedSidebar} />
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
</SettingAccordion>
|
||||||
|
|
||||||
<div class="mt-4 flex justify-end">
|
<div class="mt-4 flex justify-end">
|
||||||
<Button shape="round" type="submit" size="small" onclick={() => handleSave()}>{$t('save')}</Button>
|
<Button shape="round" type="submit" size="small" onclick={() => handleSave()}>{$t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+4
-1
@@ -3,6 +3,7 @@
|
|||||||
import { getFileSize } from '$lib/utils/asset-utils';
|
import { getFileSize } from '$lib/utils/asset-utils';
|
||||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
import { type AssetResponseDto } from '@immich/sdk';
|
import { type AssetResponseDto } from '@immich/sdk';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
asset: AssetResponseDto;
|
asset: AssetResponseDto;
|
||||||
@@ -25,7 +26,9 @@
|
|||||||
<Thumbnail asset={toTimelineAsset(asset)} readonly onClick={() => onViewAsset(asset)} thumbnailSize={boxWidth} />
|
<Thumbnail asset={toTimelineAsset(asset)} readonly onClick={() => onViewAsset(asset)} thumbnailSize={boxWidth} />
|
||||||
|
|
||||||
{#if !!asset.libraryId}
|
{#if !!asset.libraryId}
|
||||||
<div class="absolute inset-e-3 bottom-1 rounded-xl bg-red-500 px-4 py-1 text-xs transition-colors">External</div>
|
<div class="absolute inset-e-3 bottom-1 rounded-xl bg-red-500 px-4 py-1 text-xs transition-colors">
|
||||||
|
{$t('external')}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 truncate px-4 text-center text-sm font-normal" title={asset.originalFileName}>
|
<div class="mt-4 truncate px-4 text-center text-sm font-normal" title={asset.originalFileName}>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import {
|
import {
|
||||||
AudioCodec,
|
AudioCodec,
|
||||||
CQMode,
|
CQMode,
|
||||||
|
HlsVideoResolution,
|
||||||
ToneMapping,
|
ToneMapping,
|
||||||
TranscodeHWAccel,
|
TranscodeHWAccel,
|
||||||
TranscodePolicy,
|
TranscodePolicy,
|
||||||
@@ -401,9 +402,45 @@
|
|||||||
title={$t('admin.transcoding_realtime_enabled')}
|
title={$t('admin.transcoding_realtime_enabled')}
|
||||||
subtitle={$t('admin.transcoding_realtime_enabled_description')}
|
subtitle={$t('admin.transcoding_realtime_enabled_description')}
|
||||||
bind:checked={configToEdit.ffmpeg.realtime.enabled}
|
bind:checked={configToEdit.ffmpeg.realtime.enabled}
|
||||||
isEdited={configToEdit.ffmpeg.realtime.enabled !== configToEdit.ffmpeg.realtime.enabled}
|
isEdited={configToEdit.ffmpeg.realtime.enabled !== config.ffmpeg.realtime.enabled}
|
||||||
{disabled}
|
{disabled}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SettingCheckboxes
|
||||||
|
label={$t('admin.transcoding_realtime_video_codecs')}
|
||||||
|
desc={$t('admin.transcoding_realtime_video_codecs_description')}
|
||||||
|
disabled={disabled || !configToEdit.ffmpeg.realtime.enabled}
|
||||||
|
bind:value={configToEdit.ffmpeg.realtime.videoCodecs}
|
||||||
|
name="realtimeVideoCodecs"
|
||||||
|
options={[
|
||||||
|
{ value: VideoCodec.H264, text: 'H.264' },
|
||||||
|
{ value: VideoCodec.Hevc, text: 'HEVC' },
|
||||||
|
{ value: VideoCodec.Av1, text: 'AV1' },
|
||||||
|
]}
|
||||||
|
isEdited={!isEqual(
|
||||||
|
sortBy(configToEdit.ffmpeg.realtime.videoCodecs),
|
||||||
|
sortBy(config.ffmpeg.realtime.videoCodecs),
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingCheckboxes
|
||||||
|
label={$t('admin.transcoding_realtime_resolutions')}
|
||||||
|
desc={$t('admin.transcoding_realtime_resolutions_description')}
|
||||||
|
disabled={disabled || !configToEdit.ffmpeg.realtime.enabled}
|
||||||
|
bind:value={configToEdit.ffmpeg.realtime.resolutions}
|
||||||
|
name="realtimeResolutions"
|
||||||
|
options={[
|
||||||
|
{ value: HlsVideoResolution.$480, text: '480p' },
|
||||||
|
{ value: HlsVideoResolution.$720, text: '720p' },
|
||||||
|
{ value: HlsVideoResolution.$1080, text: '1080p' },
|
||||||
|
{ value: HlsVideoResolution.$1440, text: '1440p' },
|
||||||
|
{ value: HlsVideoResolution.$2160, text: '2160p' },
|
||||||
|
]}
|
||||||
|
isEdited={!isEqual(
|
||||||
|
sortBy(configToEdit.ffmpeg.realtime.resolutions),
|
||||||
|
sortBy(config.ffmpeg.realtime.resolutions),
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script lang="ts" generics="T extends string">
|
<script lang="ts" generics="T extends string | number">
|
||||||
import { Checkbox, Label } from '@immich/ui';
|
import { Checkbox, Label } from '@immich/ui';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from 'svelte/easing';
|
||||||
|
|||||||
@@ -44,4 +44,7 @@ export const preferencesFactory = Sync.makeFactory<UserPreferencesResponseDto>({
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
sidebarWeb: false,
|
sidebarWeb: false,
|
||||||
},
|
},
|
||||||
|
recentlyAdded: {
|
||||||
|
sidebarWeb: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user