mirror of
https://github.com/immich-app/immich
synced 2026-08-22 13:13:05 +00:00
Merge branch 'main' into feat/album-asset-workflow-trigger
This commit is contained in:
commit
87c28ef5fb
201 changed files with 2540 additions and 3430 deletions
4
.github/workflows/build-mobile.yml
vendored
4
.github/workflows/build-mobile.yml
vendored
|
|
@ -94,7 +94,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
working_directory: ./mobile
|
||||
|
|
@ -216,7 +216,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
working_directory: ./mobile
|
||||
|
|
|
|||
4
.github/workflows/check-openapi.yml
vendored
4
.github/workflows/check-openapi.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: Check for breaking API changes
|
||||
uses: oasdiff/oasdiff-action/breaking@4bdf89062dad92caea71236f59704b04d3ce9682 # v0.1.8
|
||||
uses: oasdiff/oasdiff-action/breaking@0ab8ad204b00d25acc5ae87106281433e288d0c1 # v0.1.10
|
||||
with:
|
||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||
revision: open-api/immich-openapi-specs.json
|
||||
|
|
@ -43,7 +43,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
working_directory: ./mobile
|
||||
|
|
|
|||
4
.github/workflows/cli.yml
vendored
4
.github/workflows/cli.yml
vendored
|
|
@ -44,7 +44,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ jobs:
|
|||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
with:
|
||||
registry: ghcr.io
|
||||
|
|
|
|||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -58,7 +58,7 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
|
@ -71,7 +71,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||
uses: github/codeql-action/autobuild@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
|
@ -84,6 +84,6 @@ jobs:
|
|||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
|
|
|||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
|
|
@ -61,7 +61,7 @@ jobs:
|
|||
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
|
@ -91,7 +91,7 @@ jobs:
|
|||
suffix: ['']
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
|
|
|||
2
.github/workflows/docs-build.yml
vendored
2
.github/workflows/docs-build.yml
vendored
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
|
|||
2
.github/workflows/docs-deploy.yml
vendored
2
.github/workflows/docs-deploy.yml
vendored
|
|
@ -143,7 +143,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
|
|||
2
.github/workflows/docs-destroy.yml
vendored
2
.github/workflows/docs-destroy.yml
vendored
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
|
|||
2
.github/workflows/fix-format.yml
vendored
2
.github/workflows/fix-format.yml
vendored
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
|
|||
1
.github/workflows/pr-label-validation.yml
vendored
1
.github/workflows/pr-label-validation.yml
vendored
|
|
@ -8,7 +8,6 @@ permissions: {}
|
|||
|
||||
jobs:
|
||||
validate-release-label:
|
||||
if: ${{ !startsWith(github.event.pull_request.head.ref, 'mergify/merge-queue/') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
|
|
|
|||
2
.github/workflows/prepare-release.yml
vendored
2
.github/workflows/prepare-release.yml
vendored
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
ref: main
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
|
|||
2
.github/workflows/sdk.yml
vendored
2
.github/workflows/sdk.yml
vendored
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
|
|||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
|
|
@ -64,7 +64,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
working_directory: ./mobile
|
||||
|
|
|
|||
28
.github/workflows/test.yml
vendored
28
.github/workflows/test.yml
vendored
|
|
@ -91,7 +91,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -184,7 +184,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -297,7 +297,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -348,7 +348,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -382,7 +382,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -606,7 +606,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
working_directory: ./mobile
|
||||
|
|
@ -644,7 +644,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -676,7 +676,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -729,7 +729,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
@ -787,7 +787,7 @@ jobs:
|
|||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ We ask you not to open PRs generated with an LLM. We find that code generated li
|
|||
|
||||
If you use an LLM to translate a PR description or title, that is fine. Please make sure however that you stick to the PR template and that the text is written concisely.
|
||||
|
||||
LLMs must not be used to fix GitHub issues labelled good first issue. These issues are generally not urgent, and are intended to be learning opportunities for new contributors to get familiar with the codebase. Whether you are a newcomer or not, fully automating the process of fixing this issue squanders the learning opportunity and doesn’t add much value to the project. Using LLMs to fix issues labelled as “good first issue” is forbidden, and those PRs will be closed automatically.
|
||||
|
||||
Misrepresenting LLM use, contribution farming (automated low-effort PRs), or repeatedly hitting auto-close rules may be grounds for a block at maintainer discretion.
|
||||
|
||||
## Feature freezes
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ services:
|
|||
container_name: immich_prometheus
|
||||
ports:
|
||||
- 9090:9090
|
||||
image: prom/prometheus@sha256:3c42b892cf723fa54d2f262c37a0e1f80aa8c8ddb1da7b9b0df9455a35a7f893
|
||||
image: prom/prometheus@sha256:508729e0e2d18e11fd742a5a5ca70e557b940a93948c3c95fd0123a6fd538b69
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus-data:/prometheus
|
||||
|
|
@ -97,7 +97,7 @@ services:
|
|||
command: ['./run.sh', '-disable-reporting']
|
||||
ports:
|
||||
- 3000:3000
|
||||
image: grafana/grafana:12.4.6-ubuntu@sha256:35c75489ff2e2e69c53977a180de491643d51fdb32108f9335317527978daed7
|
||||
image: grafana/grafana:12.4.6-ubuntu@sha256:124ddf024f95e61b562a02112b47bc64de75973370cf440b1356d31cd8178844
|
||||
volumes:
|
||||
- grafana-data:/var/lib/grafana
|
||||
|
||||
|
|
|
|||
|
|
@ -2188,7 +2188,7 @@
|
|||
"upload_day_count": "{date}: {count, plural, one {# upload} other {# uploads}}",
|
||||
"upload_details": "Upload Details",
|
||||
"upload_error_with_count": "Upload error for {count, plural, one {# asset} other {# assets}}",
|
||||
"upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}, refresh the page to see new upload assets.",
|
||||
"upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}.",
|
||||
"upload_finished": "Upload finished",
|
||||
"upload_progress": "Remaining {remaining, number} - Processed {processed, number}/{total, number}",
|
||||
"upload_skipped_duplicates": "Skipped {count, plural, one {# duplicate asset} other {# duplicate assets}}",
|
||||
|
|
|
|||
|
|
@ -160,8 +160,8 @@ ENV IMMICH_SOURCE_REF=${BUILD_SOURCE_REF}
|
|||
ENV IMMICH_SOURCE_COMMIT=${BUILD_SOURCE_COMMIT}
|
||||
ENV IMMICH_SOURCE_URL=https://github.com/immich-app/immich/commit/${BUILD_SOURCE_COMMIT}
|
||||
|
||||
# store matplotlib config in /cache subfolder to enable rootless deployment with a single volume
|
||||
ENV MPLCONFIGDIR=/cache/matplotlib
|
||||
# store huggingface in /cache subfolder to enable rootless deployment with a single volume
|
||||
ENV HF_HOME=/cache/hf-cache
|
||||
|
||||
ENTRYPOINT ["tini", "--"]
|
||||
CMD ["python", "-m", "immich_ml"]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import json
|
||||
from typing import Any, Iterator
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Callable, Iterator
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from numpy.typing import NDArray
|
||||
from PIL import Image
|
||||
|
||||
from immich_ml.config import log
|
||||
|
|
@ -17,11 +16,6 @@ def pil_image() -> Image.Image:
|
|||
return Image.new("RGB", (600, 800))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cv_image(pil_image: Image.Image) -> NDArray[np.float32]:
|
||||
return np.asarray(pil_image)[:, :, ::-1] # PIL uses RGB while cv2 uses BGR
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_model() -> Iterator[mock.Mock]:
|
||||
with mock.patch("immich_ml.models.cache.from_model_type", autospec=True) as mocked:
|
||||
|
|
@ -129,6 +123,18 @@ def ort_session() -> Iterator[mock.Mock]:
|
|||
yield mocked
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def stub_session() -> Callable[..., mock.Mock]:
|
||||
def _make(shape: tuple[Any, ...], outputs: Any = None, name: str = "input.1") -> mock.Mock:
|
||||
session = mock.Mock()
|
||||
session.get_inputs.return_value = [SimpleNamespace(name=name, shape=shape)]
|
||||
if outputs is not None:
|
||||
session.run.return_value = outputs
|
||||
return session
|
||||
|
||||
return _make
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def ann_session() -> Iterator[mock.Mock]:
|
||||
with mock.patch("immich_ml.sessions.ann.Ann") as mocked:
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class InferenceModel(ABC):
|
|||
self.load()
|
||||
if model_kwargs:
|
||||
self.configure(**model_kwargs)
|
||||
return self._predict(*inputs)
|
||||
return self._predict(*inputs, **model_kwargs)
|
||||
|
||||
@abstractmethod
|
||||
def _predict(self, *inputs: Any, **model_kwargs: Any) -> Any: ...
|
||||
|
|
|
|||
74
machine-learning/immich_ml/models/facial_recognition/_ops.py
Normal file
74
machine-learning/immich_ml/models/facial_recognition/_ops.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
"""
|
||||
Host-side geometry for the fused face models.
|
||||
|
||||
The decode, NMS and Umeyama similarity are ports of insightface (Apache-2.0, https://github.com/deepinsight/insightface)
|
||||
"""
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from immich_ml.models.transforms import ensure_dims
|
||||
|
||||
DET_SIZE = 640
|
||||
ALIGNED_SIZE = 112
|
||||
|
||||
# the FPN levels for which the fused detector emits a (scores, boxes, kps) triple,
|
||||
# and the anchors each feature-map cell carries, laid out anchor-major
|
||||
DET_STRIDES = (8, 16, 32)
|
||||
ANCHORS_PER_CELL = 2
|
||||
|
||||
# canonical ArcFace 5-point template for a 112x112 crop
|
||||
ARCFACE_DST = np.array(
|
||||
[[38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366], [41.5493, 92.3655], [70.7299, 92.2041]],
|
||||
dtype=np.float32,
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=len(DET_STRIDES))
|
||||
def _anchor_centers(size: int, stride: int) -> NDArray[np.float32]:
|
||||
ys, xs = np.mgrid[: size // stride, : size // stride]
|
||||
centers = np.stack([xs, ys], axis=-1, dtype=np.float32).reshape(-1, 2) * stride
|
||||
return np.repeat(centers, ANCHORS_PER_CELL, axis=0)
|
||||
|
||||
|
||||
def decode_scrfd(
|
||||
heads: list[NDArray[np.float32]], size: int = DET_SIZE
|
||||
) -> tuple[NDArray[np.float32], NDArray[np.float32], NDArray[np.float32]]:
|
||||
heads = [ensure_dims(head, 4) for head in heads]
|
||||
scores, boxes, kps = [], [], []
|
||||
for level, stride in enumerate(DET_STRIDES):
|
||||
centers = _anchor_centers(size, stride)
|
||||
distance = heads[level + len(DET_STRIDES)][0] * stride
|
||||
offsets = heads[level + 2 * len(DET_STRIDES)][0] * stride
|
||||
scores.append(heads[level][0].squeeze(-1))
|
||||
boxes.append(np.concatenate([centers[None] - distance[:, :, :2], centers[None] + distance[:, :, 2:]], axis=2))
|
||||
kps.append(np.tile(centers, offsets.shape[2] // 2) + offsets)
|
||||
|
||||
return np.concatenate(scores, axis=1), np.concatenate(boxes, axis=1), np.concatenate(kps, axis=1)
|
||||
|
||||
|
||||
def nms(boxes: NDArray[np.float32], scores: NDArray[np.float32], threshold: float = 0.4) -> NDArray[np.intp]:
|
||||
wh = np.column_stack([boxes[:, 0], boxes[:, 1], boxes[:, 2] - boxes[:, 0], boxes[:, 3] - boxes[:, 1]])
|
||||
keep = cv2.dnn.NMSBoxes(wh.tolist(), scores.tolist(), 0.0, threshold) # NMSBoxes treats the inputs as Sequences
|
||||
return np.asarray(keep, dtype=np.intp).reshape(-1)
|
||||
|
||||
|
||||
def umeyama(src: NDArray[np.float32], dst: NDArray[np.float32]) -> NDArray[np.float32]:
|
||||
src_mean, dst_mean = src.mean(0), dst.mean(0)
|
||||
src_c, dst_c = src - src_mean, dst - dst_mean
|
||||
cov = dst_c.T @ src_c / len(src)
|
||||
u, s, vt = np.linalg.svd(cov)
|
||||
d = np.sign(np.linalg.det(u @ vt))
|
||||
diag = np.diag([1.0, d])
|
||||
rotation = u @ diag @ vt
|
||||
scale = np.trace(np.diag(s) @ diag) / (src_c**2).sum() * len(src)
|
||||
translation = dst_mean - scale * rotation @ src_mean
|
||||
return np.hstack([scale * rotation, translation[:, None]], dtype=np.float32)
|
||||
|
||||
|
||||
def align_face(image: NDArray[np.uint8], kps: NDArray[np.float32]) -> NDArray[np.float32]:
|
||||
matrix = umeyama(kps, ARCFACE_DST)
|
||||
return cv2.warpAffine(image, matrix, (ALIGNED_SIZE, ALIGNED_SIZE)).astype(np.float32)
|
||||
|
|
@ -1,41 +1,31 @@
|
|||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from insightface.model_zoo import RetinaFace
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from immich_ml.models.base import InferenceModel
|
||||
from immich_ml.models.transforms import decode_cv2
|
||||
from immich_ml.schemas import FaceDetectionOutput, ModelSession, ModelTask, ModelType
|
||||
from immich_ml.models.transforms import decode_pil, letterbox, normalize
|
||||
from immich_ml.schemas import FaceDetectionOutput, ModelTask, ModelType
|
||||
|
||||
from ._ops import DET_SIZE, decode_scrfd, nms
|
||||
|
||||
|
||||
class FaceDetector(InferenceModel):
|
||||
depends = []
|
||||
identity = (ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
|
||||
|
||||
def __init__(self, model_name: str, min_score: float = 0.7, **model_kwargs: Any) -> None:
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
super().__init__(model_name, **model_kwargs)
|
||||
def _predict(self, inputs: NDArray[np.uint8] | bytes, minScore: float) -> FaceDetectionOutput:
|
||||
canvas, scale = letterbox(decode_pil(inputs), DET_SIZE)
|
||||
blob = normalize(canvas.astype(np.float32), mean=127.5, std=128).transpose(2, 0, 1)[None]
|
||||
|
||||
def _load(self) -> ModelSession:
|
||||
session = self._make_session(self.model_path)
|
||||
self.model = RetinaFace(session=session)
|
||||
self.model.prepare(ctx_id=0, det_thresh=self.min_score, input_size=(640, 640))
|
||||
input_name = self.session.get_inputs()[0].name
|
||||
heads = self.session.run(None, {input_name: blob})
|
||||
scores, boxes, kps = decode_scrfd(heads, DET_SIZE)
|
||||
|
||||
return session
|
||||
candidates = scores >= minScore
|
||||
scores, boxes, kps = scores[candidates], boxes[candidates] / scale, kps[candidates] / scale
|
||||
keep = nms(boxes, scores)
|
||||
|
||||
def _predict(self, inputs: NDArray[np.uint8] | bytes) -> FaceDetectionOutput:
|
||||
inputs = decode_cv2(inputs)
|
||||
|
||||
bboxes, landmarks = self._detect(inputs)
|
||||
return {
|
||||
"boxes": bboxes[:, :4].round(),
|
||||
"scores": bboxes[:, 4],
|
||||
"landmarks": landmarks,
|
||||
"boxes": boxes[keep].round(),
|
||||
"scores": scores[keep],
|
||||
"landmarks": kps[keep].reshape(-1, 5, 2),
|
||||
}
|
||||
|
||||
def _detect(self, inputs: NDArray[np.uint8] | bytes) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
|
||||
return self.model.detect(inputs) # type: ignore
|
||||
|
||||
def configure(self, **kwargs: Any) -> None:
|
||||
self.model.det_thresh = kwargs.pop("minScore", self.model.det_thresh)
|
||||
|
|
|
|||
|
|
@ -4,15 +4,13 @@ from typing import Any
|
|||
import numpy as np
|
||||
import onnx
|
||||
import onnxruntime as ort
|
||||
from insightface.model_zoo import ArcFaceONNX
|
||||
from insightface.utils.face_align import norm_crop
|
||||
from numpy.typing import NDArray
|
||||
from onnx.tools.update_model_dims import update_inputs_outputs_dims
|
||||
from PIL import Image
|
||||
|
||||
from immich_ml.config import log, settings
|
||||
from immich_ml.models.base import InferenceModel
|
||||
from immich_ml.models.transforms import decode_cv2, serialize_np_array
|
||||
from immich_ml.models.transforms import decode_pil, normalize, serialize_np_array
|
||||
from immich_ml.schemas import (
|
||||
FaceDetectionOutput,
|
||||
FacialRecognitionOutput,
|
||||
|
|
@ -22,6 +20,8 @@ from immich_ml.schemas import (
|
|||
ModelType,
|
||||
)
|
||||
|
||||
from ._ops import align_face
|
||||
|
||||
|
||||
class FaceRecognizer(InferenceModel):
|
||||
depends = [(ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)]
|
||||
|
|
@ -37,10 +37,6 @@ class FaceRecognizer(InferenceModel):
|
|||
if (not self.batch_size or self.batch_size > 1) and str(session.get_inputs()[0].shape[0]) != "batch":
|
||||
self._add_batch_axis(self.model_path)
|
||||
session = self._make_session(self.model_path)
|
||||
self.model = ArcFaceONNX(
|
||||
self.model_path_for_format(ModelFormat.ONNX).as_posix(),
|
||||
session=session,
|
||||
)
|
||||
return session
|
||||
|
||||
def _predict(
|
||||
|
|
@ -48,20 +44,21 @@ class FaceRecognizer(InferenceModel):
|
|||
) -> FacialRecognitionOutput:
|
||||
if faces["boxes"].shape[0] == 0:
|
||||
return []
|
||||
inputs = decode_cv2(inputs)
|
||||
cropped_faces = self._crop(inputs, faces)
|
||||
embeddings = self._predict_batch(cropped_faces)
|
||||
image = np.asarray(decode_pil(inputs), dtype=np.uint8)
|
||||
crops = np.stack([align_face(image, kps).transpose(2, 0, 1) for kps in faces["landmarks"]])
|
||||
embeddings = self._predict_batch(normalize(crops, mean=127.5, std=127.5))
|
||||
return self.postprocess(faces, embeddings)
|
||||
|
||||
def _predict_batch(self, cropped_faces: list[NDArray[np.uint8]]) -> NDArray[np.float32]:
|
||||
if not self.batch_size or len(cropped_faces) <= self.batch_size:
|
||||
embeddings: NDArray[np.float32] = self.model.get_feat(cropped_faces)
|
||||
return embeddings
|
||||
def _predict_batch(self, crops: NDArray[np.float32]) -> NDArray[np.float32]:
|
||||
input_name = self.session.get_inputs()[0].name
|
||||
if not self.batch_size or crops.shape[0] <= self.batch_size:
|
||||
return self.session.run(None, {input_name: crops})[0]
|
||||
|
||||
batch_embeddings: list[NDArray[np.float32]] = []
|
||||
for i in range(0, len(cropped_faces), self.batch_size):
|
||||
batch_embeddings.append(self.model.get_feat(cropped_faces[i : i + self.batch_size]))
|
||||
return np.concatenate(batch_embeddings, axis=0)
|
||||
batches = [
|
||||
self.session.run(None, {input_name: crops[i : i + self.batch_size]})[0]
|
||||
for i in range(0, crops.shape[0], self.batch_size)
|
||||
]
|
||||
return np.concatenate(batches, axis=0)
|
||||
|
||||
def postprocess(self, faces: FaceDetectionOutput, embeddings: NDArray[np.float32]) -> FacialRecognitionOutput:
|
||||
return [
|
||||
|
|
@ -73,9 +70,6 @@ class FaceRecognizer(InferenceModel):
|
|||
for (x1, y1, x2, y2), embedding, score in zip(faces["boxes"], embeddings, faces["scores"])
|
||||
]
|
||||
|
||||
def _crop(self, image: NDArray[np.uint8], faces: FaceDetectionOutput) -> list[NDArray[np.uint8]]:
|
||||
return [norm_crop(image, landmark) for landmark in faces["landmarks"]]
|
||||
|
||||
def _add_batch_axis(self, model_path: Path) -> None:
|
||||
log.debug(f"Adding batch axis to model {model_path}")
|
||||
proto = onnx.load(model_path)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ class TextDetector(InferenceModel):
|
|||
return OrtSession(self.model_path)
|
||||
|
||||
# partly adapted from RapidOCR
|
||||
def _predict(self, inputs: Image.Image) -> TextDetectionOutput:
|
||||
# TODO: take the options as parameters instead of applying them through `configure`
|
||||
def _predict(self, inputs: Image.Image, **kwargs: Any) -> TextDetectionOutput:
|
||||
w, h = inputs.size
|
||||
if w < 32 or h < 32:
|
||||
return self._empty
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ class TextRecognizer(InferenceModel):
|
|||
)
|
||||
return session
|
||||
|
||||
def _predict(self, img: Image.Image, texts: TextDetectionOutput) -> TextRecognitionOutput:
|
||||
# TODO: take the options as parameters instead of applying them through `configure`
|
||||
def _predict(self, img: Image.Image, texts: TextDetectionOutput, **kwargs: Any) -> TextRecognitionOutput:
|
||||
boxes, box_scores = texts["boxes"], texts["scores"]
|
||||
if boxes.shape[0] == 0:
|
||||
return self._empty
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import string
|
||||
from io import BytesIO
|
||||
from typing import IO
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
|
@ -12,11 +11,10 @@ _PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in
|
|||
_PUNCTUATION_TRANS = str.maketrans("", "", string.punctuation)
|
||||
|
||||
|
||||
def resize_pil(img: Image.Image, size: int) -> Image.Image:
|
||||
def resize_pil(img: Image.Image, size: int, resample: Image.Resampling = Image.Resampling.BICUBIC) -> Image.Image:
|
||||
if img.width < img.height:
|
||||
return img.resize((size, int((img.height / img.width) * size)), resample=Image.Resampling.BICUBIC)
|
||||
else:
|
||||
return img.resize((int((img.width / img.height) * size), size), resample=Image.Resampling.BICUBIC)
|
||||
return img.resize((size, int((img.height / img.width) * size)), resample=resample)
|
||||
return img.resize((int((img.width / img.height) * size), size), resample=resample)
|
||||
|
||||
|
||||
# https://stackoverflow.com/a/60883103
|
||||
|
|
@ -36,7 +34,9 @@ def to_numpy(img: Image.Image) -> NDArray[np.float32]:
|
|||
def normalize(
|
||||
img: NDArray[np.float32], mean: float | NDArray[np.float32], std: float | NDArray[np.float32]
|
||||
) -> NDArray[np.float32]:
|
||||
return (img - mean) / std
|
||||
img *= 1.0 / std
|
||||
img -= mean / std
|
||||
return img
|
||||
|
||||
|
||||
def get_pil_resampling(resample: str) -> Image.Resampling:
|
||||
|
|
@ -47,26 +47,21 @@ def pil_to_cv2(image: Image.Image) -> NDArray[np.uint8]:
|
|||
return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) # type: ignore
|
||||
|
||||
|
||||
def decode_pil(image_bytes: bytes | IO[bytes] | Image.Image) -> Image.Image:
|
||||
if isinstance(image_bytes, Image.Image):
|
||||
return image_bytes
|
||||
image: Image.Image = Image.open(BytesIO(image_bytes) if isinstance(image_bytes, bytes) else image_bytes)
|
||||
def decode_pil(image_bytes: bytes | Image.Image | NDArray[np.uint8]) -> Image.Image:
|
||||
image: Image.Image
|
||||
match image_bytes:
|
||||
case Image.Image():
|
||||
image = image_bytes
|
||||
case np.ndarray():
|
||||
image = Image.fromarray(image_bytes)
|
||||
case bytes():
|
||||
image = Image.open(BytesIO(image_bytes))
|
||||
image.load()
|
||||
if not image.mode == "RGB":
|
||||
image = image.convert("RGB")
|
||||
return image
|
||||
|
||||
|
||||
def decode_cv2(image_bytes: NDArray[np.uint8] | bytes | Image.Image) -> NDArray[np.uint8]:
|
||||
match image_bytes:
|
||||
case bytes() | memoryview() | bytearray():
|
||||
return pil_to_cv2(decode_pil(image_bytes)) # pillow is much faster than cv2
|
||||
case Image.Image():
|
||||
return pil_to_cv2(image_bytes)
|
||||
case _:
|
||||
return image_bytes
|
||||
|
||||
|
||||
def clean_text(text: str, canonicalize: bool = False) -> str:
|
||||
text = " ".join(text.split())
|
||||
if canonicalize:
|
||||
|
|
@ -78,3 +73,21 @@ def clean_text(text: str, canonicalize: bool = False) -> str:
|
|||
# TODO: use this in a less invasive way
|
||||
def serialize_np_array(arr: NDArray[np.float32]) -> str:
|
||||
return orjson.dumps(arr, option=orjson.OPT_SERIALIZE_NUMPY).decode()
|
||||
|
||||
|
||||
def letterbox(image: NDArray[np.uint8] | Image.Image, size: int) -> tuple[NDArray[np.uint8], float]:
|
||||
if isinstance(image, Image.Image):
|
||||
image = np.asarray(image)
|
||||
height, width = image.shape[:2]
|
||||
if height > width:
|
||||
new_height, new_width = size, int(size * width / height)
|
||||
else:
|
||||
new_width, new_height = size, int(size * height / width)
|
||||
|
||||
canvas = np.zeros((size, size, 3), dtype=np.uint8)
|
||||
cv2.resize(image, (new_width, new_height), dst=canvas[:new_height, :new_width])
|
||||
return canvas, new_height / height
|
||||
|
||||
|
||||
def ensure_dims(array: NDArray[np.float32], ndim: int) -> NDArray[np.float32]:
|
||||
return array if array.ndim >= ndim else np.expand_dims(array, axis=tuple(range(ndim - array.ndim)))
|
||||
|
|
|
|||
|
|
@ -14,10 +14,7 @@ class ORJSONResponse(JSONResponse):
|
|||
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
value: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
__str__ = str.__str__
|
||||
|
||||
|
||||
class BoundingBox(TypedDict):
|
||||
|
|
@ -63,7 +60,7 @@ ModelIdentity = tuple[ModelType, ModelTask]
|
|||
|
||||
class SessionNode(Protocol):
|
||||
@property
|
||||
def name(self) -> str | None: ...
|
||||
def name(self) -> str: ...
|
||||
|
||||
@property
|
||||
def shape(self) -> tuple[int, ...]: ...
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ class AnnSession:
|
|||
|
||||
def get_inputs(self) -> list[SessionNode]:
|
||||
shapes = self.ann.input_shapes[self.model]
|
||||
return [AnnNode(None, s) for s in shapes]
|
||||
return [AnnNode(f"input.{i + 1}", s) for i, s in enumerate(shapes)]
|
||||
|
||||
def get_outputs(self) -> list[SessionNode]:
|
||||
shapes = self.ann.output_shapes[self.model]
|
||||
return [AnnNode(None, s) for s in shapes]
|
||||
return [AnnNode(f"output.{i + 1}", s) for i, s in enumerate(shapes)]
|
||||
|
||||
def run(
|
||||
self,
|
||||
|
|
@ -54,5 +54,5 @@ class AnnSession:
|
|||
|
||||
|
||||
class AnnNode(NamedTuple):
|
||||
name: str | None
|
||||
name: str
|
||||
shape: tuple[int, ...]
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class RknnSession:
|
|||
|
||||
|
||||
class RknnNode(NamedTuple):
|
||||
name: str | None
|
||||
name: str
|
||||
shape: tuple[int, ...]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ dependencies = [
|
|||
"fastapi>=0.95.2,<1.0",
|
||||
"gunicorn>=21.1.0",
|
||||
"huggingface-hub>=1.0,<2.0",
|
||||
"insightface>=0.7.3,<2.0",
|
||||
"numpy>=2.4.0,<3.0",
|
||||
"onnx>=1.22.0",
|
||||
"opencv-python-headless>=4.7.0.72,<5.0",
|
||||
"orjson>=3.9.5",
|
||||
"pillow>=12.2,<13",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from types import SimpleNamespace
|
|||
from typing import Any, Callable
|
||||
from unittest import mock
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
import orjson
|
||||
|
|
@ -516,7 +515,7 @@ class TestAnnSession:
|
|||
inputs = session.get_inputs()
|
||||
|
||||
assert len(inputs) == 1
|
||||
assert inputs[0].name is None
|
||||
assert inputs[0].name == "input.1"
|
||||
assert inputs[0].shape == (1, 3, 224, 224)
|
||||
|
||||
def test_get_outputs(self, ann_session: mock.Mock) -> None:
|
||||
|
|
@ -527,7 +526,7 @@ class TestAnnSession:
|
|||
outputs = session.get_outputs()
|
||||
|
||||
assert len(outputs) == 1
|
||||
assert outputs[0].name is None
|
||||
assert outputs[0].name == "output.1"
|
||||
assert outputs[0].shape == (1, 3, 224, 224)
|
||||
|
||||
def test_run(self, ann_session: mock.Mock, mocker: MockerFixture) -> None:
|
||||
|
|
@ -781,89 +780,189 @@ class TestCLIP:
|
|||
assert np.allclose(tokens["attention_mask"], np.array([mock_attention_mask], dtype=np.int32), atol=0)
|
||||
|
||||
|
||||
def make_scrfd_heads(detections: list[tuple[int, int, float]]) -> list[np.ndarray]:
|
||||
"""Build the 9 head tensors a SCRFD keypoint model emits at 640x640.
|
||||
|
||||
`detections` is a list of (cell_x, cell_y, score) placed on the stride-8 level.
|
||||
Distances and keypoint offsets are fixed so the decoded geometry is known by
|
||||
hand rather than derived from the code under test:
|
||||
box = [cx - 1*8, cy - 2*8, cx + 3*8, cy + 4*8]
|
||||
kps = [cx, cy] + [0, 1, 2, ... 9] * 8, reshaped to 5 points
|
||||
"""
|
||||
heads: list[np.ndarray] = []
|
||||
counts = [(640 // stride) ** 2 * 2 for stride in (8, 16, 32)]
|
||||
for channels in (1, 4, 10):
|
||||
for n in counts:
|
||||
heads.append(np.zeros((n, channels), dtype=np.float32))
|
||||
for cell_x, cell_y, score in detections:
|
||||
i = 2 * (cell_y * 80 + cell_x) # anchor-major, 2 anchors per cell
|
||||
heads[0][i] = score
|
||||
heads[3][i] = [1, 2, 3, 4]
|
||||
heads[6][i] = np.arange(10)
|
||||
return heads
|
||||
|
||||
|
||||
def expected_box(cell_x: int, cell_y: int) -> list[float]:
|
||||
cx, cy = cell_x * 8, cell_y * 8
|
||||
return [cx - 8, cy - 16, cx + 24, cy + 32]
|
||||
|
||||
|
||||
def expected_landmarks(cell_x: int, cell_y: int) -> np.ndarray:
|
||||
cx, cy = cell_x * 8, cell_y * 8
|
||||
return (np.tile([cx, cy], 5) + np.arange(10) * 8).reshape(5, 2).astype(np.float32)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def batch_axis(mocker: MockerFixture) -> SimpleNamespace:
|
||||
"""Patches for the `_add_batch_axis` path: the onnx module it rewrites the model
|
||||
through, and the download it would otherwise trigger on load."""
|
||||
mocker.patch("immich_ml.models.base.InferenceModel.download")
|
||||
return SimpleNamespace(
|
||||
onnx=mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True),
|
||||
update_dims=mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class TestFaceRecognition:
|
||||
def test_set_min_score(self, snapshot_download: mock.Mock, ort_session: mock.Mock, path: mock.Mock) -> None:
|
||||
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx"
|
||||
|
||||
face_detector = FaceDetector("buffalo_s", min_score=0.5, cache_dir="test_cache")
|
||||
face_detector.load()
|
||||
|
||||
assert face_detector.min_score == 0.5
|
||||
assert face_detector.model.det_thresh == 0.5
|
||||
|
||||
def test_detection(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None:
|
||||
def test_detection(self, stub_session: Callable[..., mock.Mock], mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(FaceDetector, "load")
|
||||
face_detector = FaceDetector("buffalo_s", min_score=0.0, cache_dir="test_cache")
|
||||
face_detector = FaceDetector("buffalo_s", cache_dir="test_cache")
|
||||
|
||||
det_model = mock.Mock()
|
||||
num_faces = 2
|
||||
bbox = np.random.rand(num_faces, 4).astype(np.float32)
|
||||
scores = np.array([[0.67]] * num_faces).astype(np.float32)
|
||||
kpss = np.random.rand(num_faces, 5, 2).astype(np.float32)
|
||||
det_model.detect.return_value = (np.concatenate([bbox, scores], axis=-1), kpss)
|
||||
face_detector.model = det_model
|
||||
session = stub_session((1, 3, 640, 640), outputs=make_scrfd_heads([(10, 10, 0.9), (50, 50, 0.8)]))
|
||||
face_detector.session = session
|
||||
|
||||
faces = face_detector.predict(cv_image)
|
||||
faces = face_detector.predict(Image.new("RGB", (640, 640)), minScore=0.7)
|
||||
|
||||
assert isinstance(faces, dict)
|
||||
assert isinstance(faces.get("boxes", None), np.ndarray)
|
||||
assert isinstance(faces.get("landmarks", None), np.ndarray)
|
||||
assert isinstance(faces.get("scores", None), np.ndarray)
|
||||
assert np.equal(faces["boxes"], bbox.round()).all()
|
||||
assert np.equal(faces["landmarks"], kpss).all()
|
||||
assert np.equal(faces["scores"], scores).all()
|
||||
det_model.detect.assert_called_once()
|
||||
assert set(faces) == {"boxes", "scores", "landmarks"}
|
||||
# NMS returns highest score first
|
||||
assert faces["boxes"].tolist() == [expected_box(10, 10), expected_box(50, 50)]
|
||||
assert np.allclose(faces["scores"], [0.9, 0.8])
|
||||
assert faces["landmarks"].shape == (2, 5, 2)
|
||||
assert np.allclose(faces["landmarks"][0], expected_landmarks(10, 10))
|
||||
assert np.allclose(faces["landmarks"][1], expected_landmarks(50, 50))
|
||||
|
||||
def test_recognition(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None:
|
||||
def test_detection_applies_min_score_per_request(
|
||||
self, stub_session: Callable[..., mock.Mock], mocker: MockerFixture
|
||||
) -> None:
|
||||
mocker.patch.object(FaceDetector, "load")
|
||||
face_detector = FaceDetector("buffalo_s", cache_dir="test_cache")
|
||||
|
||||
session = stub_session((1, 3, 640, 640), outputs=make_scrfd_heads([(10, 10, 0.9), (50, 50, 0.5)]))
|
||||
face_detector.session = session
|
||||
|
||||
# the threshold is a request parameter, so the same loaded model must honour both
|
||||
assert face_detector.predict(Image.new("RGB", (640, 640)), minScore=0.7)["boxes"].shape[0] == 1
|
||||
assert face_detector.predict(Image.new("RGB", (640, 640)), minScore=0.4)["boxes"].shape[0] == 2
|
||||
|
||||
def test_detection_scales_boxes_back_to_the_original_image(
|
||||
self, stub_session: Callable[..., mock.Mock], mocker: MockerFixture
|
||||
) -> None:
|
||||
mocker.patch.object(FaceDetector, "load")
|
||||
face_detector = FaceDetector("buffalo_s", cache_dir="test_cache")
|
||||
|
||||
session = stub_session((1, 3, 640, 640), outputs=make_scrfd_heads([(10, 10, 0.9)]))
|
||||
face_detector.session = session
|
||||
|
||||
# a 320x320 image is letterboxed up to 640, so coordinates come back halved
|
||||
faces = face_detector.predict(Image.new("RGB", (320, 320)), minScore=0.7)
|
||||
|
||||
assert faces["boxes"].tolist() == [[v / 2 for v in expected_box(10, 10)]]
|
||||
assert np.allclose(faces["landmarks"][0], expected_landmarks(10, 10) / 2)
|
||||
|
||||
def test_recognition(self, stub_session: Callable[..., mock.Mock], mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(FaceRecognizer, "load")
|
||||
mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.ort.get_available_providers",
|
||||
return_value=["CPUExecutionProvider"],
|
||||
)
|
||||
face_recognizer = FaceRecognizer("buffalo_s", min_score=0.0, cache_dir="test_cache")
|
||||
face_recognizer = FaceRecognizer("buffalo_s", cache_dir="test_cache")
|
||||
|
||||
# a uniform grey image whose crops land wholly inside it, so every sampled
|
||||
# pixel is 128 and the normalised value the session receives is exact
|
||||
image = Image.new("RGB", (600, 800), (128, 128, 128))
|
||||
num_faces = 2
|
||||
arcface_dst = np.array(
|
||||
[[38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366], [41.5493, 92.3655], [70.7299, 92.2041]],
|
||||
dtype=np.float32,
|
||||
)
|
||||
kpss = np.stack([arcface_dst * 2 + [200, 300], arcface_dst * 2 + [220, 320]]).astype(np.float32)
|
||||
bbox = np.random.rand(num_faces, 4).astype(np.float32)
|
||||
scores = np.array([0.67] * num_faces).astype(np.float32)
|
||||
kpss = np.random.rand(num_faces, 5, 2).astype(np.float32)
|
||||
faces = {"boxes": bbox, "landmarks": kpss, "scores": scores}
|
||||
embeddings = np.random.rand(num_faces, 512).astype(np.float32)
|
||||
|
||||
rec_model = mock.Mock()
|
||||
embedding = np.random.rand(num_faces, 512).astype(np.float32)
|
||||
rec_model.get_feat.return_value = embedding
|
||||
face_recognizer.model = rec_model
|
||||
session = stub_session(("batch", 3, 112, 112), outputs=[embeddings])
|
||||
face_recognizer.session = session
|
||||
|
||||
faces = face_recognizer.predict(cv_image, faces)
|
||||
faces = face_recognizer.predict(image, {"boxes": bbox, "landmarks": kpss, "scores": scores})
|
||||
|
||||
assert isinstance(faces, list)
|
||||
assert len(faces) == num_faces
|
||||
for face in faces:
|
||||
assert isinstance(face.get("boundingBox"), dict)
|
||||
assert set(face["boundingBox"]) == {"x1", "y1", "x2", "y2"}
|
||||
assert all(isinstance(val, np.float32) for val in face["boundingBox"].values())
|
||||
embedding_str = face.get("embedding")
|
||||
assert isinstance(embedding_str, str)
|
||||
embedding = orjson.loads(embedding_str)
|
||||
embedding = orjson.loads(face["embedding"])
|
||||
assert isinstance(embedding, list)
|
||||
assert len(embedding) == 512
|
||||
assert isinstance(face.get("score", None), np.float32)
|
||||
|
||||
rec_model.get_feat.assert_called_once()
|
||||
call_args = rec_model.get_feat.call_args_list[0].args
|
||||
assert len(call_args) == 1
|
||||
assert isinstance(call_args[0], list)
|
||||
assert isinstance(call_args[0][0], np.ndarray)
|
||||
assert call_args[0][0].shape == (112, 112, 3)
|
||||
session.run.assert_called_once()
|
||||
crops = session.run.call_args.args[1]["input.1"]
|
||||
assert crops.shape == (num_faces, 3, 112, 112)
|
||||
assert crops.dtype == np.float32
|
||||
# mean/std 127.5, not raw 0-255. atol is loose enough for the float32 cancellation
|
||||
# in normalize's scale-then-subtract, but still rejects a wrong mean or std
|
||||
assert np.allclose(crops, (128 - 127.5) / 127.5, atol=1e-6)
|
||||
|
||||
def test_recognition_returns_early_without_faces(self, pil_image: Image.Image, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(FaceRecognizer, "load")
|
||||
mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.ort.get_available_providers",
|
||||
return_value=["CPUExecutionProvider"],
|
||||
)
|
||||
face_recognizer = FaceRecognizer("buffalo_s", cache_dir="test_cache")
|
||||
session = mock.Mock()
|
||||
face_recognizer.session = session
|
||||
|
||||
empty = {
|
||||
"boxes": np.empty((0, 4), dtype=np.float32),
|
||||
"landmarks": np.empty((0, 5, 2), dtype=np.float32),
|
||||
"scores": np.empty(0, dtype=np.float32),
|
||||
}
|
||||
|
||||
assert face_recognizer.predict(pil_image, empty) == []
|
||||
session.run.assert_not_called()
|
||||
|
||||
def test_recognition_batches_when_batch_size_is_set(
|
||||
self, pil_image: Image.Image, stub_session: Callable[..., mock.Mock], mocker: MockerFixture
|
||||
) -> None:
|
||||
mocker.patch.object(FaceRecognizer, "load")
|
||||
mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.ort.get_available_providers",
|
||||
return_value=["CPUExecutionProvider"],
|
||||
)
|
||||
face_recognizer = FaceRecognizer("buffalo_s", cache_dir="test_cache")
|
||||
face_recognizer.batch_size = 2
|
||||
|
||||
num_faces = 5
|
||||
session = stub_session((1, 3, 112, 112))
|
||||
session.run.side_effect = lambda _, feed: [np.zeros((feed["input.1"].shape[0], 512), dtype=np.float32)]
|
||||
face_recognizer.session = session
|
||||
|
||||
faces = {
|
||||
"boxes": np.random.rand(num_faces, 4).astype(np.float32),
|
||||
"landmarks": (np.random.rand(num_faces, 5, 2) * 100).astype(np.float32),
|
||||
"scores": np.array([0.67] * num_faces, dtype=np.float32),
|
||||
}
|
||||
assert len(face_recognizer.predict(pil_image, faces)) == num_faces
|
||||
assert session.run.call_count == 3 # 2 + 2 + 1
|
||||
assert [c.args[1]["input.1"].shape[0] for c in session.run.call_args_list] == [2, 2, 1]
|
||||
|
||||
def test_recognition_adds_batch_axis_for_ort(
|
||||
self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||
self, batch_axis: SimpleNamespace, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||
) -> None:
|
||||
onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True)
|
||||
update_dims = mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||
)
|
||||
mocker.patch("immich_ml.models.base.InferenceModel.download")
|
||||
mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX")
|
||||
mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.ort.get_available_providers",
|
||||
return_value=["CPUExecutionProvider"],
|
||||
|
|
@ -884,24 +983,20 @@ class TestFaceRecognition:
|
|||
output_dims.type.tensor_type.shape.dim = [SimpleNamespace(dim_value=size) for size in [1, 800]]
|
||||
proto.graph.output = [output_dims]
|
||||
|
||||
onnx.load.return_value = proto
|
||||
batch_axis.onnx.load.return_value = proto
|
||||
|
||||
face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path)
|
||||
face_recognizer.load()
|
||||
|
||||
assert face_recognizer.batch_size is None
|
||||
update_dims.assert_called_once_with(proto, {"input.1": ["batch", 3, 224, 224]}, {"output.1": ["batch", 800]})
|
||||
onnx.save.assert_called_once_with(update_dims.return_value, face_recognizer.model_path)
|
||||
batch_axis.update_dims.assert_called_once_with(
|
||||
proto, {"input.1": ["batch", 3, 224, 224]}, {"output.1": ["batch", 800]}
|
||||
)
|
||||
batch_axis.onnx.save.assert_called_once_with(batch_axis.update_dims.return_value, face_recognizer.model_path)
|
||||
|
||||
def test_recognition_does_not_add_batch_axis_if_exists(
|
||||
self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||
self, batch_axis: SimpleNamespace, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||
) -> None:
|
||||
onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True)
|
||||
update_dims = mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||
)
|
||||
mocker.patch("immich_ml.models.base.InferenceModel.download")
|
||||
mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX")
|
||||
mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.ort.get_available_providers",
|
||||
return_value=["CPUExecutionProvider"],
|
||||
|
|
@ -916,88 +1011,53 @@ class TestFaceRecognition:
|
|||
face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path)
|
||||
face_recognizer.load()
|
||||
|
||||
# batching is available here, so the axis is only skipped because it already exists
|
||||
assert face_recognizer.batch_size is None
|
||||
update_dims.assert_not_called()
|
||||
onnx.load.assert_not_called()
|
||||
onnx.save.assert_not_called()
|
||||
batch_axis.update_dims.assert_not_called()
|
||||
batch_axis.onnx.load.assert_not_called()
|
||||
batch_axis.onnx.save.assert_not_called()
|
||||
|
||||
def test_recognition_does_not_add_batch_axis_for_armnn(
|
||||
self, ann_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||
@pytest.mark.parametrize(
|
||||
("session_fixture", "suffix", "model_kwargs", "available_providers"),
|
||||
[
|
||||
pytest.param("ann_session", ".armnn", {"model_format": ModelFormat.ARMNN}, None, id="armnn"),
|
||||
pytest.param(
|
||||
"ort_session", ".onnx", {}, ["OpenVINOExecutionProvider", "CPUExecutionProvider"], id="openvino"
|
||||
),
|
||||
pytest.param(
|
||||
"ort_session", ".onnx", {}, ["MIGraphXExecutionProvider", "CPUExecutionProvider"], id="migraphx"
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_recognition_does_not_add_batch_axis_when_batching_is_unsupported(
|
||||
self,
|
||||
request: pytest.FixtureRequest,
|
||||
batch_axis: SimpleNamespace,
|
||||
path: mock.Mock,
|
||||
ort_pybind: mock.Mock,
|
||||
mocker: MockerFixture,
|
||||
session_fixture: str,
|
||||
suffix: str,
|
||||
model_kwargs: dict[str, Any],
|
||||
available_providers: list[str] | None,
|
||||
) -> None:
|
||||
onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True)
|
||||
update_dims = mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||
)
|
||||
mocker.patch("immich_ml.models.base.InferenceModel.download")
|
||||
mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX")
|
||||
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".armnn"
|
||||
|
||||
inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))]
|
||||
outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))]
|
||||
ann_session.return_value.get_inputs.return_value = inputs
|
||||
ann_session.return_value.get_outputs.return_value = outputs
|
||||
|
||||
face_recognizer = FaceRecognizer("buffalo_s", model_format=ModelFormat.ARMNN, cache_dir=path)
|
||||
face_recognizer.load()
|
||||
|
||||
assert face_recognizer.batch_size == 1
|
||||
update_dims.assert_not_called()
|
||||
onnx.load.assert_not_called()
|
||||
onnx.save.assert_not_called()
|
||||
|
||||
def test_recognition_does_not_add_batch_axis_for_openvino(
|
||||
self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||
) -> None:
|
||||
onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True)
|
||||
update_dims = mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||
)
|
||||
mocker.patch("immich_ml.models.base.InferenceModel.download")
|
||||
mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX")
|
||||
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx"
|
||||
|
||||
inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))]
|
||||
outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))]
|
||||
ort_session.return_value.get_inputs.return_value = inputs
|
||||
ort_session.return_value.get_outputs.return_value = outputs
|
||||
|
||||
face_recognizer = FaceRecognizer(
|
||||
"buffalo_s", model_format=ModelFormat.ARMNN, cache_dir=path, providers=["OpenVINOExecutionProvider"]
|
||||
)
|
||||
face_recognizer.load()
|
||||
|
||||
assert face_recognizer.batch_size == 1
|
||||
update_dims.assert_not_called()
|
||||
onnx.load.assert_not_called()
|
||||
onnx.save.assert_not_called()
|
||||
|
||||
def test_recognition_does_not_add_batch_axis_for_migraphx(
|
||||
self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||
) -> None:
|
||||
onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True)
|
||||
update_dims = mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||
)
|
||||
mocker.patch("immich_ml.models.base.InferenceModel.download")
|
||||
mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX")
|
||||
session = request.getfixturevalue(session_fixture)
|
||||
ort_pybind.get_available_openvino_device_ids.return_value = ["CPU"]
|
||||
mocker.patch(
|
||||
"immich_ml.models.facial_recognition.recognition.ort.get_available_providers",
|
||||
return_value=["MIGraphXExecutionProvider", "CPUExecutionProvider"],
|
||||
return_value=available_providers,
|
||||
)
|
||||
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx"
|
||||
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = suffix
|
||||
session.return_value.get_inputs.return_value = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))]
|
||||
session.return_value.get_outputs.return_value = [SimpleNamespace(name="output.1", shape=(1, 800))]
|
||||
|
||||
inputs = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))]
|
||||
outputs = [SimpleNamespace(name="output.1", shape=(1, 800))]
|
||||
ort_session.return_value.get_inputs.return_value = inputs
|
||||
ort_session.return_value.get_outputs.return_value = outputs
|
||||
|
||||
face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path)
|
||||
face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path, **model_kwargs)
|
||||
face_recognizer.load()
|
||||
|
||||
assert face_recognizer.batch_size == 1
|
||||
update_dims.assert_not_called()
|
||||
onnx.load.assert_not_called()
|
||||
onnx.save.assert_not_called()
|
||||
batch_axis.update_dims.assert_not_called()
|
||||
batch_axis.onnx.load.assert_not_called()
|
||||
batch_axis.onnx.save.assert_not_called()
|
||||
|
||||
def test_set_custom_max_batch_size(self, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(settings, "max_batch_size", MaxBatchSize(facial_recognition=2))
|
||||
|
|
@ -1045,10 +1105,10 @@ class TestOcr:
|
|||
|
||||
rapid_recognizer.assert_called_once_with(
|
||||
OcrOptions(
|
||||
session=ort_session.return_value,
|
||||
rec_batch_num=6,
|
||||
rec_img_shape=(3, 48, 320),
|
||||
model_root_dir=text_recognizer.cache_dir,
|
||||
session=ort_session.return_value,
|
||||
rec_batch_num=6,
|
||||
rec_img_shape=(3, 48, 320),
|
||||
model_root_dir=text_recognizer.cache_dir,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1063,10 +1123,10 @@ class TestOcr:
|
|||
|
||||
rapid_recognizer.assert_called_once_with(
|
||||
OcrOptions(
|
||||
session=ort_session.return_value,
|
||||
rec_batch_num=4,
|
||||
rec_img_shape=(3, 48, 320),
|
||||
model_root_dir=text_recognizer.cache_dir,
|
||||
session=ort_session.return_value,
|
||||
rec_batch_num=4,
|
||||
rec_img_shape=(3, 48, 320),
|
||||
model_root_dir=text_recognizer.cache_dir,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1083,10 +1143,10 @@ class TestOcr:
|
|||
|
||||
rapid_recognizer.assert_called_once_with(
|
||||
OcrOptions(
|
||||
session=ort_session.return_value,
|
||||
rec_batch_num=6,
|
||||
rec_img_shape=(3, 48, 320),
|
||||
model_root_dir=text_recognizer.cache_dir,
|
||||
session=ort_session.return_value,
|
||||
rec_batch_num=6,
|
||||
rec_img_shape=(3, 48, 320),
|
||||
model_root_dir=text_recognizer.cache_dir,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
674
machine-learning/uv.lock
generated
674
machine-learning/uv.lock
generated
|
|
@ -25,23 +25,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/37/d7/15d67e05b235d1ed8c3ce61688fe4d84130e72af1657acadfaac3479f4cf/aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d", size = 28199, upload-time = "2024-09-25T13:20:22.688Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "albumentations"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "opencv-python-headless" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "qudida" },
|
||||
{ name = "scikit-image" },
|
||||
{ name = "scipy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/14/d6/8dd5b690d28a332a0b2c3179a345808b5d4c7ad5ddc079b7e116098dff35/albumentations-1.3.1.tar.gz", hash = "sha256:a6a38388fe546c568071e8c82f414498e86c9ed03c08b58e7a88b31cf7a244c6", size = 176371, upload-time = "2023-06-10T07:44:32.36Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/f6/c486cedb4f75147232f32ec4c97026714cfef7c7e247a1f0427bc5489f66/albumentations-1.3.1-py3-none-any.whl", hash = "sha256:6b641d13733181d9ecdc29550e6ad580d1bfa9d25e2213a66940062f25e291bd", size = 125706, upload-time = "2023-06-10T07:44:30.373Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-doc"
|
||||
version = "0.0.4"
|
||||
|
|
@ -326,88 +309,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/31/28/d28211d29bcc3620b1fece85a65ce5bb22f18670a03cd28ea4b75ede270c/configargparse-1.7.1-py3-none-any.whl", hash = "sha256:8b586a31f9d873abd1ca527ffbe58863c99f36d896e2829779803125e83be4b6", size = 25607, upload-time = "2025-05-23T14:26:15.923Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "contourpy"
|
||||
version = "1.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.13.4"
|
||||
|
|
@ -512,46 +413,6 @@ toml = [
|
|||
{ name = "tomli", marker = "python_full_version <= '3.11'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cycler"
|
||||
version = "0.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cython"
|
||||
version = "3.0.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/68/09/ffb61f29b8e3d207c444032b21328327d753e274ea081bc74e009827cc81/Cython-3.0.8.tar.gz", hash = "sha256:8333423d8fd5765e7cceea3a9985dd1e0a5dfeb2734629e1a2ed2d6233d39de6", size = 2744096, upload-time = "2024-01-10T11:01:02.155Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/a7/f4a0bc9a80e23b380daa2ebb4879bf434aaa0b3b91f7ad8a7f9762b4bd1b/Cython-3.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aae26f9663e50caf9657148403d9874eea41770ecdd6caf381d177c2b1bb82ba", size = 3113615, upload-time = "2024-01-10T11:34:05.899Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/e9/e9295df74246c165b91253a473bfa179debf739c9bee961cbb3ae56c2b79/Cython-3.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:547eb3cdb2f8c6f48e6865d5a741d9dd051c25b3ce076fbca571727977b28ac3", size = 3436320, upload-time = "2024-01-10T11:02:08.689Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/2c/6a887c957aa53e44f928119dea628a5dfacc8e875424034f5fecac9daba4/Cython-3.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a567d4b9ba70b26db89d75b243529de9e649a2f56384287533cf91512705bee", size = 3591755, upload-time = "2024-01-10T11:02:11.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/b8/f9c97bae6281da50b3ecb1f7fef0f7f7851eae084609b364717a2b366bf1/Cython-3.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d1426263b0e82fb22bda8ea60dc77a428581cc19e97741011b938445d383f1", size = 3636099, upload-time = "2024-01-10T11:02:15.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/ae/cd055c2c081c67a6fcad1d8d17d82bd6395b14c6741e3a938f40318c8bc5/Cython-3.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c26daaeccda072459b48d211415fd1e5507c06bcd976fa0d5b8b9f1063467d7b", size = 3458119, upload-time = "2024-01-10T11:02:19.103Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/ab/ac6f5548d6194f4bb2fc8c6c996aa7369f0fa1403e4d4de787d9e9309b27/Cython-3.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:289ce7838208211cd166e975865fd73b0649bf118170b6cebaedfbdaf4a37795", size = 3614418, upload-time = "2024-01-10T11:02:22.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/e2/3e3e448b7a94887bec3235bcb71957b6681dc42b4536459f8f54d46fa936/Cython-3.0.8-cp311-cp311-win32.whl", hash = "sha256:c8aa05f5e17f8042a3be052c24f2edc013fb8af874b0bf76907d16c51b4e7871", size = 2572819, upload-time = "2024-01-10T11:02:25.976Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/7d/58635941dfbb5b4e197adb88080b9cbfb230dc3b75683698a530a1989bdb/Cython-3.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:000dc9e135d0eec6ecb2b40a5b02d0868a2f8d2e027a41b0fe16a908a9e6de02", size = 2784167, upload-time = "2024-01-10T11:02:28.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/8e/28f8c6109990eef7317ab7e43644092b49a88a39f9373dcd19318946df09/Cython-3.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d3fe31db55685d8cb97d43b0ec39ef614fcf660f83c77ed06aa670cb0e164f", size = 3135638, upload-time = "2024-01-10T11:34:22.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/1f/4720cb682b8ed1ab9749dea35351a66dd29b6a022628cce038415660c384/Cython-3.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e24791ddae2324e88e3c902a765595c738f19ae34ee66bfb1a6dac54b1833419", size = 3340052, upload-time = "2024-01-10T11:02:32.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/47/ec3fceb9e8f7d6fa130216b8740038e1df7c8e5f215bba363fcf1272a6c1/Cython-3.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f020fa1c0552052e0660790b8153b79e3fc9a15dbd8f1d0b841fe5d204a6ae6", size = 3510079, upload-time = "2024-01-10T11:02:35.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/31/b458127851e248effb909e2791b55870914863cde7c60b94db5ee65d7867/Cython-3.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18bfa387d7a7f77d7b2526af69a65dbd0b731b8d941aaff5becff8e21f6d7717", size = 3573972, upload-time = "2024-01-10T11:02:39.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/d5/ca6513844d0634abd05ba12304053a454bb70441a9520afa9897d4300156/Cython-3.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fe81b339cffd87c0069c6049b4d33e28bdd1874625ee515785bf42c9fdff3658", size = 3356158, upload-time = "2024-01-10T11:02:42.125Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/59/98a87b6264f4ad45c820db13c4ec657567476efde020c49443cc842a86af/Cython-3.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80fd94c076e1e1b1ee40a309be03080b75f413e8997cddcf401a118879863388", size = 3522312, upload-time = "2024-01-10T11:02:45.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/cb/132115d07a0b9d4f075e0741db70a5416b424dcd875b2bb0dd805e818222/Cython-3.0.8-cp312-cp312-win32.whl", hash = "sha256:85077915a93e359a9b920280d214dc0cf8a62773e1f3d7d30fab8ea4daed670c", size = 2602579, upload-time = "2024-01-10T11:02:48.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/69/cb4620287cd9ef461103e122c0a2ae7f7ecf183e02510676fb5a15c95b05/Cython-3.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:0cb2dcc565c7851f75d496f724a384a790fab12d1b82461b663e66605bec429a", size = 2791268, upload-time = "2024-01-10T11:02:51.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/7f/f584f5d15323feb897d42ef0e9d910649e2150d7a30cf7e7a8cc1d236e6f/Cython-3.0.8-py2.py3-none-any.whl", hash = "sha256:171b27051253d3f9108e9759e504ba59ff06e7f7ba944457f94deaf9c21bf0b6", size = 1168213, upload-time = "2024-01-10T11:00:56.857Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "easydict"
|
||||
version = "1.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/d2/deb3296d08097fedd622d423c0ec8b68b78c1704b3f1545326f6ce05c75c/easydict-1.11.tar.gz", hash = "sha256:dcb1d2ed28eb300c8e46cd371340373abc62f7c14d6dea74fdfc6f1069061c78", size = 6644, upload-time = "2023-10-23T23:01:37.686Z" }
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.136.3"
|
||||
|
|
@ -627,55 +488,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/6f/12/d5c79ee252793ffe845d58a913197bfa02ae9a0b5c9bc3dc4b58d477b9e7/flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1", size = 26744, upload-time = "2023-05-26T17:35:14.269Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.61.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsspec"
|
||||
version = "2023.12.2"
|
||||
|
|
@ -959,19 +771,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imageio"
|
||||
version = "2.33.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "pillow" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/38/f4c568318c656352d211eec6954460dc3af0b7583a6682308f8a66e4c19b/imageio-2.33.1.tar.gz", hash = "sha256:78722d40b137bd98f5ec7312119f8aea9ad2049f76f434748eb306b6937cc1ce", size = 387374, upload-time = "2023-12-11T02:26:44.715Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/69/3aaa69cb0748e33e644fda114c9abd3186ce369edd4fca11107e9f39c6a7/imageio-2.33.1-py3-none-any.whl", hash = "sha256:c5094c48ccf6b2e6da8b4061cd95e1209380afafcbeae4a4e280938cce227e1d", size = 313345, upload-time = "2023-12-11T02:26:42.724Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "immich-ml"
|
||||
version = "3.1.0"
|
||||
|
|
@ -981,8 +780,8 @@ dependencies = [
|
|||
{ name = "fastapi" },
|
||||
{ name = "gunicorn" },
|
||||
{ name = "huggingface-hub" },
|
||||
{ name = "insightface" },
|
||||
{ name = "numpy" },
|
||||
{ name = "onnx" },
|
||||
{ name = "opencv-python-headless" },
|
||||
{ name = "orjson" },
|
||||
{ name = "pillow" },
|
||||
|
|
@ -1062,8 +861,8 @@ requires-dist = [
|
|||
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
|
||||
{ name = "gunicorn", specifier = ">=21.1.0" },
|
||||
{ name = "huggingface-hub", specifier = ">=1.0,<2.0" },
|
||||
{ name = "insightface", specifier = ">=0.7.3,<2.0" },
|
||||
{ name = "numpy", specifier = ">=2.4.0,<3.0" },
|
||||
{ name = "onnx", specifier = ">=1.22.0" },
|
||||
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
||||
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
||||
{ name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" },
|
||||
|
|
@ -1133,27 +932,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insightface"
|
||||
version = "0.7.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "albumentations" },
|
||||
{ name = "cython" },
|
||||
{ name = "easydict" },
|
||||
{ name = "matplotlib" },
|
||||
{ name = "numpy" },
|
||||
{ name = "onnx" },
|
||||
{ name = "pillow" },
|
||||
{ name = "prettytable" },
|
||||
{ name = "requests" },
|
||||
{ name = "scikit-image" },
|
||||
{ name = "scikit-learn" },
|
||||
{ name = "scipy" },
|
||||
{ name = "tqdm" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/8d/0f4af90999ca96cf8cb846eb5ae27c5ef5b390f9c090dd19e4fa76364c13/insightface-0.7.3.tar.gz", hash = "sha256:f191f719612ebb37018f41936814500544cd0f86e6fcd676c023f354c668ddf7", size = 439490, upload-time = "2023-04-02T08:01:54.541Z" }
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.1.2"
|
||||
|
|
@ -1175,65 +953,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271, upload-time = "2024-05-05T23:41:59.928Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "joblib"
|
||||
version = "1.3.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/15/0f/d3b33b9f106dddef461f6df1872b7881321b247f3d255b87f61a7636f7fe/joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1", size = 1987720, upload-time = "2023-08-09T09:23:40.503Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/40/d551139c85db202f1f384ba8bcf96aca2f329440a844f924c8a0040b6d02/joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9", size = 302207, upload-time = "2023-08-09T09:23:34.583Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kiwisolver"
|
||||
version = "1.4.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2d/226779e405724344fc678fcc025b812587617ea1a48b9442628b688e85ea/kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", size = 97552, upload-time = "2023-08-24T09:30:39.861Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/eb/9e099ad7c47c279995d2d20474e1821100a5f10f847739bd65b1c1f02442/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", size = 127403, upload-time = "2023-08-24T09:28:39.3Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/94/695922e71288855fc7cace3bdb52edda9d7e50edba77abb0c9d7abb51e96/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", size = 68156, upload-time = "2023-08-24T09:28:40.301Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/fe/23d7fa78f7c66086d196406beb1fb2eaf629dd7adc01c3453033303d17fa/kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", size = 66166, upload-time = "2023-08-24T09:28:41.235Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/68/f472bf16c9141bb1bea5c0b8c66c68fc1ccb048efdbd8f0872b92125724e/kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", size = 1334300, upload-time = "2023-08-24T09:28:42.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/26/b4569d1f29751fca22ee915b4ebfef5974f4ef239b3335fc072882bd62d9/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", size = 1426579, upload-time = "2023-08-24T09:28:43.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/a3/804fc7c8bf233806ec0321c9da35971578620f2ab4fafe67d76100b3ce52/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", size = 1541360, upload-time = "2023-08-24T09:28:45.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/ef/286e1d26524854f6fbd6540e8364d67a8857d61038ac743e11edc42fe217/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", size = 1470091, upload-time = "2023-08-24T09:28:47.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/ba/17a706b232308e65f57deeccae503c268292e6a091313f6ce833a23093ea/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", size = 1426259, upload-time = "2023-08-24T09:28:49.224Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/f3/a0925611c9d6c2f37c5935a39203cadec6883aa914e013b46c84c4c2e641/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", size = 1847516, upload-time = "2023-08-24T09:28:50.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/85/82d59bb8f7c4c9bb2785138b72462cb1b161668f8230c58bbb28c0403cd5/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", size = 1946228, upload-time = "2023-08-24T09:28:52.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/3c/6a37f444c0233993881e5db3a6a1775925d4d9d2f2609bb325bb1348ed94/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", size = 1901716, upload-time = "2023-08-24T09:28:54.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/7e/180425790efc00adfd47db14e1e341cb4826516982334129012b971121a6/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", size = 1852871, upload-time = "2023-08-24T09:28:55.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/9a/13c68b2edb1fa74321e60893a9a5829788e135138e68060cf44e2d92d2c3/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f", size = 1870265, upload-time = "2023-08-24T09:28:56.855Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/0a/fa56a0fdee5da2b4c79899c0f6bd1aefb29d9438c2d66430e78793571c6b/kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", size = 46649, upload-time = "2023-08-24T09:28:58.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/37/d3c2d4ba2719059a0f12730947bbe1ad5ee8bff89e8c35319dcb2c9ddb4c/kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", size = 56116, upload-time = "2023-08-24T09:28:58.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/7a/debbce859be1a2711eb8437818107137192007b88d17b5cfdb556f457b42/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", size = 125484, upload-time = "2023-08-24T09:28:59.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/e0/bf8df75ba93b9e035cc6757dd5dcaf63084fdc1c846ae134e818bd7e0f03/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", size = 67332, upload-time = "2023-08-24T09:29:01.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/61/58bb691f6880588be3a4801d199bd776032ece07203faf3e4a8b377f7d9b/kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", size = 64987, upload-time = "2023-08-24T09:29:02.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/a3/96ac5413068b237c006f54dd8d70114e8756d70e3da7613c5aef20627e22/kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", size = 1370613, upload-time = "2023-08-24T09:29:03.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/12/f48539e6e17068b59c7f12f4d6214b973431b8e3ac83af525cafd27cebec/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", size = 1463183, upload-time = "2023-08-24T09:29:05.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/70/26c99be8eb034cc8e3f62e0760af1fbdc97a842a7cbc252f7978507d41c2/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", size = 1581248, upload-time = "2023-08-24T09:29:06.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/f6/f75f20e543639b09b2de7fc864274a5a9b96cda167a6210a1d9d19306b9d/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", size = 1508815, upload-time = "2023-08-24T09:29:07.867Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/d5/bc0f22ac108743062ab703f8d6d71c9c7b077b8839fa358700bfb81770b8/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", size = 1466042, upload-time = "2023-08-24T09:29:09.403Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/18/98142500f21d6838bcab49ec919414a1f0c6d049d21ddadf139124db6a70/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", size = 1885159, upload-time = "2023-08-24T09:29:10.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/49/a241eff9e0ee013368c1d17957f9d345b0957493c3a43d82ebb558c90b0a/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", size = 1981694, upload-time = "2023-08-24T09:29:12.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/90/9490c3de4788123041b1d600d64434f1eed809a2ce9f688075a22166b289/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", size = 1941579, upload-time = "2023-08-24T09:29:13.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/bb/a0cc488ef2aa92d7d304318c8549d3ec8dfe6dd3c2c67a44e3922b77bc4f/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", size = 1888168, upload-time = "2023-08-24T09:29:15.097Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/e9/9c0de8e45fef3d63f85eed3b1757f9aa511065942866331ef8b99421f433/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", size = 1908464, upload-time = "2023-08-24T09:29:16.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/60/4f0fd50b08f5be536ea0cef518ac7255d9dab43ca40f3b93b60e3ddf80dd/kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", size = 46473, upload-time = "2023-08-24T09:29:17.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/50/2746566bdf4a6a842d117367d05c90cfb87ac04e9e2845aa1fa21f071362/kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", size = 56004, upload-time = "2023-08-24T09:29:19.329Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-loader"
|
||||
version = "0.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librt"
|
||||
version = "0.11.0"
|
||||
|
|
@ -1374,70 +1093,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/44/44/dbaf65876e258facd65f586dde158387ab89963e7f2235551afc9c2e24c2/MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", size = 16979, upload-time = "2023-09-07T16:00:57.77Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib"
|
||||
version = "3.10.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "contourpy" },
|
||||
{ name = "cycler" },
|
||||
{ name = "fonttools" },
|
||||
{ name = "kiwisolver" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pillow" },
|
||||
{ name = "pyparsing" },
|
||||
{ name = "python-dateutil" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/91/f2939bb60b7ebf12478b030e0d7f340247390f402b3b189616aad790c366/matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076", size = 34804044, upload-time = "2025-07-31T18:09:33.805Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/c7/1f2db90a1d43710478bb1e9b57b162852f79234d28e4f48a28cc415aa583/matplotlib-3.10.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dcfc39c452c6a9f9028d3e44d2d721484f665304857188124b505b2c95e1eecf", size = 8239216, upload-time = "2025-07-31T18:07:51.947Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/6d/ca6844c77a4f89b1c9e4d481c412e1d1dbabf2aae2cbc5aa2da4a1d6683e/matplotlib-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:903352681b59f3efbf4546985142a9686ea1d616bb054b09a537a06e4b892ccf", size = 8102130, upload-time = "2025-07-31T18:07:53.65Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/1e/5e187a30cc673a3e384f3723e5f3c416033c1d8d5da414f82e4e731128ea/matplotlib-3.10.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:080c3676a56b8ee1c762bcf8fca3fe709daa1ee23e6ef06ad9f3fc17332f2d2a", size = 8666471, upload-time = "2025-07-31T18:07:55.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/c0/95540d584d7d645324db99a845ac194e915ef75011a0d5e19e1b5cee7e69/matplotlib-3.10.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b4984d5064a35b6f66d2c11d668565f4389b1119cc64db7a4c1725bc11adffc", size = 9500518, upload-time = "2025-07-31T18:07:57.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/2e/e019352099ea58b4169adb9c6e1a2ad0c568c6377c2b677ee1f06de2adc7/matplotlib-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3967424121d3a46705c9fa9bdb0931de3228f13f73d7bb03c999c88343a89d89", size = 9552372, upload-time = "2025-07-31T18:07:59.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/81/3200b792a5e8b354f31f4101ad7834743ad07b6d620259f2059317b25e4d/matplotlib-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:33775bbeb75528555a15ac29396940128ef5613cf9a2d31fb1bfd18b3c0c0903", size = 8100634, upload-time = "2025-07-31T18:08:01.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/46/a944f6f0c1f5476a0adfa501969d229ce5ae60cf9a663be0e70361381f89/matplotlib-3.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:c61333a8e5e6240e73769d5826b9a31d8b22df76c0778f8480baf1b4b01c9420", size = 7978880, upload-time = "2025-07-31T18:08:03.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/1e/c6f6bcd882d589410b475ca1fc22e34e34c82adff519caf18f3e6dd9d682/matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2", size = 8253056, upload-time = "2025-07-31T18:08:05.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/e6/d6f7d1b59413f233793dda14419776f5f443bcccb2dfc84b09f09fe05dbe/matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389", size = 8110131, upload-time = "2025-07-31T18:08:07.293Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/2b/bed8a45e74957549197a2ac2e1259671cd80b55ed9e1fe2b5c94d88a9202/matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea", size = 8669603, upload-time = "2025-07-31T18:08:09.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/a7/315e9435b10d057f5e52dfc603cd353167ae28bb1a4e033d41540c0067a4/matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468", size = 9508127, upload-time = "2025-07-31T18:08:10.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/d9/edcbb1f02ca99165365d2768d517898c22c6040187e2ae2ce7294437c413/matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369", size = 9566926, upload-time = "2025-07-31T18:08:13.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/d9/6dd924ad5616c97b7308e6320cf392c466237a82a2040381163b7500510a/matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b", size = 8107599, upload-time = "2025-07-31T18:08:15.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/f3/522dc319a50f7b0279fbe74f86f7a3506ce414bc23172098e8d2bdf21894/matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2", size = 7978173, upload-time = "2025-07-31T18:08:21.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/05/4f3c1f396075f108515e45cb8d334aff011a922350e502a7472e24c52d77/matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed", size = 8253586, upload-time = "2025-07-31T18:08:23.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/2c/e084415775aac7016c3719fe7006cdb462582c6c99ac142f27303c56e243/matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1", size = 8110715, upload-time = "2025-07-31T18:08:24.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/1b/233e3094b749df16e3e6cd5a44849fd33852e692ad009cf7de00cf58ddf6/matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7", size = 8669397, upload-time = "2025-07-31T18:08:26.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/ec/03f9e003a798f907d9f772eed9b7c6a9775d5bd00648b643ebfb88e25414/matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f", size = 9508646, upload-time = "2025-07-31T18:08:28.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/e7/c051a7a386680c28487bca27d23b02d84f63e3d2a9b4d2fc478e6a42e37e/matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4", size = 9567424, upload-time = "2025-07-31T18:08:30.726Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/c2/24302e93ff431b8f4173ee1dd88976c8d80483cadbc5d3d777cef47b3a1c/matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe", size = 8107809, upload-time = "2025-07-31T18:08:33.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/33/423ec6a668d375dad825197557ed8fbdb74d62b432c1ed8235465945475f/matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674", size = 7978078, upload-time = "2025-07-31T18:08:36.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/17/521fc16ec766455c7bb52cc046550cf7652f6765ca8650ff120aa2d197b6/matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c", size = 8295590, upload-time = "2025-07-31T18:08:38.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/12/23c28b2c21114c63999bae129fce7fd34515641c517ae48ce7b7dcd33458/matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e", size = 8158518, upload-time = "2025-07-31T18:08:40.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/f8/aae4eb25e8e7190759f3cb91cbeaa344128159ac92bb6b409e24f8711f78/matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b", size = 8691815, upload-time = "2025-07-31T18:08:42.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/ba/450c39ebdd486bd33a359fc17365ade46c6a96bf637bbb0df7824de2886c/matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea", size = 9522814, upload-time = "2025-07-31T18:08:44.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/11/9c66f6a990e27bb9aa023f7988d2d5809cb98aa39c09cbf20fba75a542ef/matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124", size = 9573917, upload-time = "2025-07-31T18:08:47.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/69/8b49394de92569419e5e05e82e83df9b749a0ff550d07631ea96ed2eb35a/matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce", size = 8181034, upload-time = "2025-07-31T18:08:48.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/23/82dc435bb98a2fc5c20dffcac8f0b083935ac28286413ed8835df40d0baa/matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154", size = 8023337, upload-time = "2025-07-31T18:08:50.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/e0/26b6cfde31f5383503ee45dcb7e691d45dadf0b3f54639332b59316a97f8/matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715", size = 8253591, upload-time = "2025-07-31T18:08:53.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/89/98488c7ef7ea20ea659af7499628c240a608b337af4be2066d644cfd0a0f/matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837", size = 8112566, upload-time = "2025-07-31T18:08:55.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/67/42294dfedc82aea55e1a767daf3263aacfb5a125f44ba189e685bab41b6f/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202", size = 9513281, upload-time = "2025-07-31T18:08:56.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/68/f258239e0cf34c2cbc816781c7ab6fca768452e6bf1119aedd2bd4a882a3/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb", size = 9780873, upload-time = "2025-07-31T18:08:59.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/64/f4881554006bd12e4558bd66778bdd15d47b00a1f6c6e8b50f6208eda4b3/matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975", size = 9568954, upload-time = "2025-07-31T18:09:01.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/f8/42779d39c3f757e1f012f2dda3319a89fb602bd2ef98ce8faf0281f4febd/matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667", size = 8237465, upload-time = "2025-07-31T18:09:03.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/f8/153fd06b5160f0cd27c8b9dd797fcc9fb56ac6a0ebf3c1f765b6b68d3c8a/matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516", size = 8108898, upload-time = "2025-07-31T18:09:05.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/ee/c4b082a382a225fe0d2a73f1f57cf6f6f132308805b493a54c8641006238/matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e", size = 8295636, upload-time = "2025-07-31T18:09:07.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/73/2195fa2099718b21a20da82dfc753bf2af58d596b51aefe93e359dd5915a/matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a", size = 8158575, upload-time = "2025-07-31T18:09:09.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/e9/a08cdb34618a91fa08f75e6738541da5cacde7c307cea18ff10f0d03fcff/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569", size = 9522815, upload-time = "2025-07-31T18:09:11.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/bb/34d8b7e0d1bb6d06ef45db01dfa560d5a67b1c40c0b998ce9ccde934bb09/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012", size = 9783514, upload-time = "2025-07-31T18:09:13.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/09/d330d1e55dcca2e11b4d304cc5227f52e2512e46828d6249b88e0694176e/matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592", size = 9573932, upload-time = "2025-07-31T18:09:15.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/3b/f70258ac729aa004aca673800a53a2b0a26d49ca1df2eaa03289a1c40f81/matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959", size = 8322003, upload-time = "2025-07-31T18:09:17.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/60/3601f8ce6d76a7c81c7f25a0e15fde0d6b66226dd187aa6d2838e6374161/matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b", size = 8153849, upload-time = "2025-07-31T18:09:19.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/d6/e921be4e1a5f7aca5194e1f016cb67ec294548e530013251f630713e456d/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:160e125da27a749481eaddc0627962990f6029811dbeae23881833a011a0907f", size = 8233224, upload-time = "2025-07-31T18:09:27.512Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/74/a2b9b04824b9c349c8f1b2d21d5af43fa7010039427f2b133a034cb09e59/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac3d50760394d78a3c9be6b28318fe22b494c4fcf6407e8fd4794b538251899b", size = 8098539, upload-time = "2025-07-31T18:09:29.629Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/66/cd29ebc7f6c0d2a15d216fb572573e8fc38bd5d6dec3bd9d7d904c0949f7/matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61", size = 8672192, upload-time = "2025-07-31T18:09:31.407Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
|
|
@ -1449,39 +1104,43 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ml-dtypes"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/78/a7/aad060393123cfb383956dca68402aff3db1e1caffd5764887ed5153f41b/ml_dtypes-0.5.3.tar.gz", hash = "sha256:95ce33057ba4d05df50b1f3cfefab22e351868a843b3b15a46c65836283670c9", size = 692316, upload-time = "2025-07-29T18:39:19.454Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/af/f1/720cb1409b5d0c05cff9040c0e9fba73fa4c67897d33babf905d5d46a070/ml_dtypes-0.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a177b882667c69422402df6ed5c3428ce07ac2c1f844d8a1314944651439458", size = 667412, upload-time = "2025-07-29T18:38:25.275Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/d5/05861ede5d299f6599f86e6bc1291714e2116d96df003cfe23cc54bcc568/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9849ce7267444c0a717c80c6900997de4f36e2815ce34ac560a3edb2d9a64cd2", size = 4964606, upload-time = "2025-07-29T18:38:27.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/dc/72992b68de367741bfab8df3b3fe7c29f982b7279d341aa5bf3e7ef737ea/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3f5ae0309d9f888fd825c2e9d0241102fadaca81d888f26f845bc8c13c1e4ee", size = 4938435, upload-time = "2025-07-29T18:38:29.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/1c/d27a930bca31fb07d975a2d7eaf3404f9388114463b9f15032813c98f893/ml_dtypes-0.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:58e39349d820b5702bb6f94ea0cb2dc8ec62ee81c0267d9622067d8333596a46", size = 206334, upload-time = "2025-07-29T18:38:30.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/d8/6922499effa616012cb8dc445280f66d100a7ff39b35c864cfca019b3f89/ml_dtypes-0.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:66c2756ae6cfd7f5224e355c893cfd617fa2f747b8bbd8996152cbdebad9a184", size = 157584, upload-time = "2025-07-29T18:38:32.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/eb/bc07c88a6ab002b4635e44585d80fa0b350603f11a2097c9d1bfacc03357/ml_dtypes-0.5.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:156418abeeda48ea4797db6776db3c5bdab9ac7be197c1233771e0880c304057", size = 663864, upload-time = "2025-07-29T18:38:33.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/89/11af9b0f21b99e6386b6581ab40fb38d03225f9de5f55cf52097047e2826/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1db60c154989af253f6c4a34e8a540c2c9dce4d770784d426945e09908fbb177", size = 4951313, upload-time = "2025-07-29T18:38:36.45Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b255acada256d1fa8c35ed07b5f6d18bc21d1556f842fbc2d5718aea2cd9e55", size = 4928805, upload-time = "2025-07-29T18:38:38.29Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/c1/85e6be4fc09c6175f36fb05a45917837f30af9a5146a5151cb3a3f0f9e09/ml_dtypes-0.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:da65e5fd3eea434ccb8984c3624bc234ddcc0d9f4c81864af611aaebcc08a50e", size = 208182, upload-time = "2025-07-29T18:38:39.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/17/cf5326d6867be057f232d0610de1458f70a8ce7b6290e4b4a277ea62b4cd/ml_dtypes-0.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:8bb9cd1ce63096567f5f42851f5843b5a0ea11511e50039a7649619abfb4ba6d", size = 161560, upload-time = "2025-07-29T18:38:41.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/87/1bcc98a66de7b2455dfb292f271452cac9edc4e870796e0d87033524d790/ml_dtypes-0.5.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5103856a225465371fe119f2fef737402b705b810bd95ad5f348e6e1a6ae21af", size = 663781, upload-time = "2025-07-29T18:38:42.984Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/2c/bd2a79ba7c759ee192b5601b675b180a3fd6ccf48ffa27fe1782d280f1a7/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cae435a68861660af81fa3c5af16b70ca11a17275c5b662d9c6f58294e0f113", size = 4956217, upload-time = "2025-07-29T18:38:44.65Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/f3/091ba84e5395d7fe5b30c081a44dec881cd84b408db1763ee50768b2ab63/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6936283b56d74fbec431ca57ce58a90a908fdbd14d4e2d22eea6d72bb208a7b7", size = 4933109, upload-time = "2025-07-29T18:38:46.405Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/24/054036dbe32c43295382c90a1363241684c4d6aaa1ecc3df26bd0c8d5053/ml_dtypes-0.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:d0f730a17cf4f343b2c7ad50cee3bd19e969e793d2be6ed911f43086460096e4", size = 208187, upload-time = "2025-07-29T18:38:48.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/3d/7dc3ec6794a4a9004c765e0c341e32355840b698f73fd2daff46f128afc1/ml_dtypes-0.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:2db74788fc01914a3c7f7da0763427280adfc9cd377e9604b6b64eb8097284bd", size = 161559, upload-time = "2025-07-29T18:38:50.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/91/e6c7a0d67a152b9330445f9f0cf8ae6eee9b83f990b8c57fe74631e42a90/ml_dtypes-0.5.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93c36a08a6d158db44f2eb9ce3258e53f24a9a4a695325a689494f0fdbc71770", size = 689321, upload-time = "2025-07-29T18:38:52.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/6c/b7b94b84a104a5be1883305b87d4c6bd6ae781504474b4cca067cb2340ec/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e44a3761f64bc009d71ddb6d6c71008ba21b53ab6ee588dadab65e2fa79eafc", size = 5274495, upload-time = "2025-07-29T18:38:53.797Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/38/6266604dffb43378055394ea110570cf261a49876fc48f548dfe876f34cc/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdf40d2aaabd3913dec11840f0d0ebb1b93134f99af6a0a4fd88ffe924928ab4", size = 5285422, upload-time = "2025-07-29T18:38:56.603Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/88/8612ff177d043a474b9408f0382605d881eeb4125ba89d4d4b3286573a83/ml_dtypes-0.5.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:aec640bd94c4c85c0d11e2733bd13cbb10438fb004852996ec0efbc6cacdaf70", size = 661182, upload-time = "2025-07-29T18:38:58.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/2b/0569a5e88b29240d373e835107c94ae9256fb2191d3156b43b2601859eff/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bda32ce212baa724e03c68771e5c69f39e584ea426bfe1a701cb01508ffc7035", size = 4956187, upload-time = "2025-07-29T18:39:00.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/66/273c2a06ae44562b104b61e6b14444da00061fd87652506579d7eb2c40b1/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c205cac07d24a29840c163d6469f61069ce4b065518519216297fc2f261f8db9", size = 4930911, upload-time = "2025-07-29T18:39:02.405Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/ab/606be3e87dc0821bd360c8c1ee46108025c31a4f96942b63907bb441b87d/ml_dtypes-0.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:cd7c0bb22d4ff86d65ad61b5dd246812e8993fbc95b558553624c33e8b6903ea", size = 216664, upload-time = "2025-07-29T18:39:03.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/a2/e900690ca47d01dffffd66375c5de8c4f8ced0f1ef809ccd3b25b3e6b8fa/ml_dtypes-0.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:9d55ea7f7baf2aed61bf1872116cefc9d0c3693b45cae3916897ee27ef4b835e", size = 160203, upload-time = "2025-07-29T18:39:05.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/21/783dfb51f40d2660afeb9bccf3612b99f6a803d980d2a09132b0f9d216ab/ml_dtypes-0.5.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:e12e29764a0e66a7a31e9b8bf1de5cc0423ea72979f45909acd4292de834ccd3", size = 689324, upload-time = "2025-07-29T18:39:07.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/f7/a82d249c711abf411ac027b7163f285487f5e615c3e0716c61033ce996ab/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19f6c3a4f635c2fc9e2aa7d91416bd7a3d649b48350c51f7f715a09370a90d93", size = 5275917, upload-time = "2025-07-29T18:39:09.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/3c/541c4b30815ab90ebfbb51df15d0b4254f2f9f1e2b4907ab229300d5e6f2/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ab039ffb40f3dc0aeeeba84fd6c3452781b5e15bef72e2d10bcb33e4bbffc39", size = 5285284, upload-time = "2025-07-29T18:39:11.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742, upload-time = "2025-11-17T22:31:44.068Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709, upload-time = "2025-11-17T22:31:46.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888, upload-time = "2025-11-17T22:31:56.907Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993, upload-time = "2025-11-17T22:31:58.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956, upload-time = "2025-11-17T22:31:59.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224, upload-time = "2025-11-17T22:32:01.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798, upload-time = "2025-11-17T22:32:02.864Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083, upload-time = "2025-11-17T22:32:04.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111, upload-time = "2025-11-17T22:32:05.546Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453, upload-time = "2025-11-17T22:32:07.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612, upload-time = "2025-11-17T22:32:08.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145, upload-time = "2025-11-17T22:32:09.782Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781, upload-time = "2025-11-17T22:32:11.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145, upload-time = "2025-11-17T22:32:12.783Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230, upload-time = "2025-11-17T22:32:14.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032, upload-time = "2025-11-17T22:32:15.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353, upload-time = "2025-11-17T22:32:16.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085, upload-time = "2025-11-17T22:32:18.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358, upload-time = "2025-11-17T22:32:19.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332, upload-time = "2025-11-17T22:32:21.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612, upload-time = "2025-11-17T22:32:22.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825, upload-time = "2025-11-17T22:32:23.766Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1606,15 +1265,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "networkx"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928, upload-time = "2023-10-28T08:41:39.364Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772, upload-time = "2023-10-28T08:41:36.945Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.4.6"
|
||||
|
|
@ -1709,7 +1359,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "onnx"
|
||||
version = "1.19.1"
|
||||
version = "1.22.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ml-dtypes" },
|
||||
|
|
@ -1717,31 +1367,26 @@ dependencies = [
|
|||
{ name = "protobuf" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/2f/c619eb65769357e9b6de9212c9a821ab39cd484448e5d6b3fb5fb0a64c6d/onnx-1.19.1.tar.gz", hash = "sha256:737524d6eb3907d3499ea459c6f01c5a96278bb3a0f2ff8ae04786fb5d7f1ed5", size = 12033525, upload-time = "2025-10-10T04:01:34.342Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/19/8ea73a64b368b75fe339771a20a02bc61ea1f551484c9e3d9d0bfbd0450f/onnx-1.22.0.tar.gz", hash = "sha256:ef40c0aaf0b643857ea9306fc7eddce17eaf9fb0407e4801f1fc5758443a38e0", size = 12024721, upload-time = "2026-06-15T12:50:05.354Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/36/07/0019c72924909e4f64b9199770630ab7b8d7914b912b03230e68f5eda7ae/onnx-1.19.1-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:17aaf5832126de0a5197a5864e4f09a764dd7681d3035135547959b4b6b77a09", size = 18320936, upload-time = "2025-10-10T04:00:04.235Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/2f/5c47acf740dc35f0decc640844260fbbdc0efa0565657c93fd7ff30f13f3/onnx-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01b292a4d0b197c45d8184545bbc8ae1df83466341b604187c1b05902cb9c920", size = 18044269, upload-time = "2025-10-10T04:00:07.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/61/6c457ee8c3a62a3cad0a4bfa4c5436bb3ac4df90c3551d40bee1224b5b51/onnx-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1839af08ab4a909e4af936b8149c27f8c64b96138981024e251906e0539d8bf9", size = 18218092, upload-time = "2025-10-10T04:00:11.135Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/d5/ab832e1369505e67926a70e9a102061f89ad01f91aa296c4b1277cb81b25/onnx-1.19.1-cp311-cp311-win32.whl", hash = "sha256:0bdbb676e3722bd32f9227c465d552689f49086f986a696419d865cb4e70b989", size = 16344809, upload-time = "2025-10-10T04:00:14.634Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/b5/6eb4611d24b85002f878ba8476b4cecbe6f9784c0236a3c5eff85236cc0a/onnx-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:1346853df5c1e3ebedb2e794cf2a51e0f33759affd655524864ccbcddad7035b", size = 16464319, upload-time = "2025-10-10T04:00:18.235Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/ff/f0e1f06420c70e20d497fec7c94a864d069943b6312bedd4224c0ab946f8/onnx-1.19.1-cp311-cp311-win_arm64.whl", hash = "sha256:2d69c280c0e665b7f923f499243b9bb84fe97970b7a4668afa0032045de602c8", size = 16437503, upload-time = "2025-10-10T04:00:21.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/07/f6c5b2cffef8c29e739616d1415aea22f7b7ef1f19c17f02b7cff71f5498/onnx-1.19.1-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:3612193a89ddbce5c4e86150869b9258780a82fb8c4ca197723a4460178a6ce9", size = 18327840, upload-time = "2025-10-10T04:00:24.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/20/0568ebd52730287ae80cac8ac893a7301c793ea1630984e2519ee92b02a9/onnx-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c2fd2f744e7a3880ad0c262efa2edf6d965d0bd02b8f327ec516ad4cb0f2f15", size = 18042539, upload-time = "2025-10-10T04:00:27.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/fd/cd7a0fd10a04f8cc5ae436b63e0022e236fe51b9dbb8ee6317fd48568c72/onnx-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:485d3674d50d789e0ee72fa6f6e174ab81cb14c772d594f992141bd744729d8a", size = 18218271, upload-time = "2025-10-10T04:00:30.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/68/cc8b8c05469fe08384b446304ad7e6256131ca0463bf6962366eebec98c0/onnx-1.19.1-cp312-cp312-win32.whl", hash = "sha256:638bc56ff1a5718f7441e887aeb4e450f37a81c6eac482040381b140bd9ba601", size = 16345111, upload-time = "2025-10-10T04:00:34.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/5e/d1cb16693598a512c2cf9ffe0841d8d8fd2c83ae8e889efd554f5aa427cf/onnx-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:bc7e2e4e163e679721e547958b5a7db875bf822cad371b7c1304aa4401a7c7a4", size = 16465621, upload-time = "2025-10-10T04:00:39.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/32/da116cc61fdef334782aa7f87a1738431dd1af1a5d1a44bd95d6d51ad260/onnx-1.19.1-cp312-cp312-win_arm64.whl", hash = "sha256:17c215b1c0f20fe93b4cbe62668247c1d2294b9bc7f6be0ca9ced28e980c07b7", size = 16437505, upload-time = "2025-10-10T04:00:42.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/b8/ab1fdfe2e8502f4dc4289fc893db35816bd20d080d8370f86e74dda5f598/onnx-1.19.1-cp313-cp313-macosx_12_0_universal2.whl", hash = "sha256:4e5f938c68c4dffd3e19e4fd76eb98d298174eb5ebc09319cdd0ec5fe50050dc", size = 18327815, upload-time = "2025-10-10T04:00:45.682Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/40/eb875745a4b92aea10e5e32aa2830f409c4d7b6f7b48ca1c4eaad96636c5/onnx-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:86e20a5984b017feeef2dbf4ceff1c7c161ab9423254968dd77d3696c38691d0", size = 18041464, upload-time = "2025-10-10T04:00:48.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/8e/8586135f40dbe4989cec4d413164bc8fc5c73d37c566f33f5ea3a7f2b6f6/onnx-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d9c467f0f29993c12f330736af87972f30adb8329b515f39d63a0db929cb2c", size = 18218244, upload-time = "2025-10-10T04:00:51.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/b5/4201254b8683129db5da3fb55aa1f7e56d0a8d45c66ce875dec21ca1ff25/onnx-1.19.1-cp313-cp313-win32.whl", hash = "sha256:65eee353a51b4e4ca3e797784661e5376e2b209f17557e04921eac9166a8752e", size = 16345330, upload-time = "2025-10-10T04:00:54.858Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/67/c6d239afbcdbeb6805432969b908b5c9f700c96d332b34e3f99518d76caf/onnx-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3bc87e38b53554b1fc9ef7b275c81c6f5c93c90a91935bb0aa8d4d498a6d48e", size = 16465567, upload-time = "2025-10-10T04:00:57.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/fe/89f1e40f5bc54595ff0dcf5391ce19e578b528973ccc74dd99800196d30d/onnx-1.19.1-cp313-cp313-win_arm64.whl", hash = "sha256:e41496f400afb980ec643d80d5164753a88a85234fa5c06afdeebc8b7d1ec252", size = 16437562, upload-time = "2025-10-10T04:01:00.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/43/b186ccbc8fe7e93643a6a6d40bbf2bb6ce4fb9469bbd3453c77e270c50ad/onnx-1.19.1-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:5f6274abf0fd74e80e78ecbb44bd44509409634525c89a9b38276c8af47dc0a2", size = 18355703, upload-time = "2025-10-10T04:01:03.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/f1/22ee4d8b8f9fa4cb1d1b9579da3b4b5187ddab33846ec5ac744af02c0e2b/onnx-1.19.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07dcd4d83584eb4bf8f21ac04c82643712e5e93ac2a0ed10121ec123cb127e1e", size = 18047830, upload-time = "2025-10-10T04:01:06.552Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/a4/8f3d51e3a095d42cdf2039a590cff06d024f2a10efbd0b1a2a6b3825f019/onnx-1.19.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1975860c3e720db25d37f1619976582828264bdcc64fa7511c321ac4fc01add3", size = 18221126, upload-time = "2025-10-10T04:01:09.77Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/0d/f9d6c2237083f1aac14b37f0b03b0d81f1147a8e2af0c3828165e0a6a67b/onnx-1.19.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9807d0e181f6070ee3a6276166acdc571575d1bd522fc7e89dba16fd6e7ffed9", size = 16465560, upload-time = "2025-10-10T04:01:13.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/70/8418a58faa7d606d6a92cab69ae8d361b3b3969bf7e7e9a65a86d5d1b674/onnx-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6ee83e6929d75005482d9f304c502ac7c9b8d6db153aa6b484dae74d0f28570", size = 18042812, upload-time = "2025-10-10T04:01:15.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/55/30825c02c92a0380ce84c3feeeec95d329fa77548ba58cb10ad4bbfd83c6/onnx-1.22.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:2d8f229a553fa440fe623ed7b36fca5e7762da3af871c3f8f8ce451df73e2914", size = 20167891, upload-time = "2026-06-15T12:49:14.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/24/cd4ab52ecaf41c3fbed674772ccbfe39041cb257b8471a47a37e48bff3f8/onnx-1.22.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a89a7cb9ba13d78f009bdec448ec82a98972589734f157022a2bff7a5973a6", size = 18892720, upload-time = "2026-06-15T12:49:16.904Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/a0/c9d9d56ceadb1c0a90a7cbec5a0510520ab6538938944fa84548e4b5b054/onnx-1.22.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d0a2bdb15eb2b3cb65c438f3423d9620d14fdce32f92380e6bb1b2e09568ef5", size = 19110720, upload-time = "2026-06-15T12:49:19.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/6e/e43e5a68d9cadde55df75310027f87127333a77e5ddcea14c73e96a10cac/onnx-1.22.0-cp311-cp311-win32.whl", hash = "sha256:239958534464612fbcb6ed23d5228aaa925b39b8773f58726809ffdccb4edd1c", size = 17083746, upload-time = "2026-06-15T12:49:22.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/57/cc0a9f2cf4522e42829d089927b4b75924d32f50dca237482e7b741df003/onnx-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:8561a2c00041c07e08db0c228593b5b4694100398685f348532af7dbb84189da", size = 17215684, upload-time = "2026-06-15T12:49:26.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/99/0f049f9eaa06c8383060c5f0a338e3a6caac8822e6e326c9162f05abf95a/onnx-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:8907b9b9389893bc0dc6314cc00ee1e3a69844e48d689eacc6a0340411a7da58", size = 17210398, upload-time = "2026-06-15T12:49:29.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/6a/481561f1093834376ed493e4ca42a73e5be0d50031f2969c86593bdc7c96/onnx-1.22.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:596fbf0490947533c1c1045ba860851dc9fb77471023dac9a71ba5b42ceab103", size = 20167081, upload-time = "2026-06-15T12:49:32.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/55/b34fc2aa30aa54b4a775402d24c4082242c720283a274fe976ac8eb94480/onnx-1.22.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae5a563f281cd9d2845622cecf6c092a57e4ee1b138f66fdbbdd4200567a5e16", size = 18889249, upload-time = "2026-06-15T12:49:34.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/a6/bd32357e6cc1ecb473afd78193d7231724f284435d2db25696ecfaaa1503/onnx-1.22.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:955e02e1f6d385b53d52f9cd7b9cdf5caf417c300bcfe3c64c6d542be763845b", size = 19106514, upload-time = "2026-06-15T12:49:37.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/9d/3af461ac6c714b8b369cb71499659932f4f12cfb066250b62f7567c3d530/onnx-1.22.0-cp312-abi3-pyemscripten_2025_0_wasm32.whl", hash = "sha256:82e9f27fc1223cb06d68a56bed6f9d3caf3d0dad1b61bce45006d529b15bd94c", size = 16966387, upload-time = "2026-06-15T12:49:40.918Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/f0/68195b5e5a53e333faf2660f5352ee43738d0e42fc5216cc6b1871a9fbfb/onnx-1.22.0-cp312-abi3-win32.whl", hash = "sha256:cc8b66b312f8f03a53e268afb67180a2d97dd12cc79e2b61361c6c0073448016", size = 17081568, upload-time = "2026-06-15T12:49:43.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/a8/734725bb703c5fabb687f79c79e51249475212b3eb37771ac4a4ac9b487f/onnx-1.22.0-cp312-abi3-win_amd64.whl", hash = "sha256:72ccebab3bac07215c204ce8848d42e78eaaa666badbf72d25cd359b9f269e3a", size = 17213290, upload-time = "2026-06-15T12:49:45.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/2a/8ce48d8ae26a8761ad4e5dc771961b155c5c3c7c8540ec7f2f2d71b69af0/onnx-1.22.0-cp312-abi3-win_arm64.whl", hash = "sha256:f3c120dcdb70ad738f3c061b32798f408ea299eb69f84dd69ab4a6bf3c2ec01f", size = 17207030, upload-time = "2026-06-15T12:49:48.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/13/47323b97846387848efb1044ded11bb94b83526f3d1fbdb37c6480d4520f/onnx-1.22.0-cp314-cp314t-macosx_12_0_universal2.whl", hash = "sha256:19e45e4af88e3fe3261458d4b8cc461957ae2782a358a3560503569bf3b23b72", size = 20176465, upload-time = "2026-06-15T12:49:51.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/0c/d3b8a7e7eee123938586c608bb9894b5723f2342b9450c0eec59fbec7099/onnx-1.22.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c21a0e59fd967a95b358e4a6e756d1f1eec2d304a83480f329f66e30d2bf0223", size = 18894028, upload-time = "2026-06-15T12:49:54.451Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/8a/da2a97ab46fe6e0cd9beb3ac14603a22f5be492f9ca347faf8233a07bb33/onnx-1.22.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2632406b8f523ef2e2873c363f90b20a3d88c0fbcfac757d3addffccf8f452c2", size = 19110420, upload-time = "2026-06-15T12:49:57.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/a3/ce984063017518307ebfaa545782fc400e593dc2d7fdf4f23ce4be1ed197/onnx-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a3a39fc4643867aecb33417fdddb11e308ee79d2d4a584b9d50cc7aec2091b13", size = 17237547, upload-time = "2026-06-15T12:50:00.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/50/257a880384a1dd502d543b0067945074d63cd17d0840e958355bc8197da8/onnx-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:8e268cdc0547e3949799ffd4a44451dc2b9080b57d0824a2db680b6ec65506f0", size = 17231391, upload-time = "2026-06-15T12:50:03.047Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2058,18 +1703,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettytable"
|
||||
version = "3.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wcwidth" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/c0/5e9c4d2a643a00a6f67578ef35485173de273a4567279e4f0c200c01386b/prettytable-3.9.0.tar.gz", hash = "sha256:f4ed94803c23073a90620b201965e5dc0bccf1760b7a7eaf3158cab8aaffdf34", size = 47874, upload-time = "2023-09-11T14:04:14.548Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/81/316b6a55a0d1f327d04cc7b0ba9d04058cb62de6c3a4d4b0df280cbe3b0b/prettytable-3.9.0-py3-none-any.whl", hash = "sha256:a71292ab7769a5de274b146b276ce938786f56c31cf7cea88b6f3775d82fe8c8", size = 27772, upload-time = "2023-09-11T14:03:45.582Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "6.33.2"
|
||||
|
|
@ -2274,15 +1907,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756, upload-time = "2023-11-21T20:43:49.423Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/37/fe/65c989f70bd630b589adfbbcd6ed238af22319e90f059946c26b4835e44b/pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db", size = 884814, upload-time = "2023-07-30T15:07:02.617Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139, upload-time = "2023-07-30T15:06:59.829Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.3"
|
||||
|
|
@ -2338,18 +1962,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324, upload-time = "2021-07-14T08:19:19.783Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702, upload-time = "2021-07-14T08:19:18.161Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.0"
|
||||
|
|
@ -2511,21 +2123,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qudida"
|
||||
version = "0.0.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "opencv-python-headless" },
|
||||
{ name = "scikit-learn" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3e/2d/bab8babd9dc9a9e4df6eb115540cee4322c1a74078fb6f3b3ebc452a22b3/qudida-0.0.4.tar.gz", hash = "sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8", size = 3100, upload-time = "2021-08-09T16:47:55.807Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/a1/a5f4bebaa31d109003909809d88aeb0d4b201463a9ea29308d9e4f9e7655/qudida-0.0.4-py3-none-any.whl", hash = "sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6", size = 3478, upload-time = "2021-08-09T16:47:54.637Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rapidocr"
|
||||
version = "3.8.1"
|
||||
|
|
@ -2661,139 +2258,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scikit-image"
|
||||
version = "0.25.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "imageio" },
|
||||
{ name = "lazy-loader" },
|
||||
{ name = "networkx" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pillow" },
|
||||
{ name = "scipy" },
|
||||
{ name = "tifffile" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scikit-learn"
|
||||
version = "1.7.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "joblib" },
|
||||
{ name = "numpy" },
|
||||
{ name = "scipy" },
|
||||
{ name = "threadpoolctl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445, upload-time = "2025-07-18T08:01:54.5Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/bd/a23177930abd81b96daffa30ef9c54ddbf544d3226b8788ce4c3ef1067b4/scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b", size = 9334838, upload-time = "2025-07-18T08:01:11.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/a1/d3a7628630a711e2ac0d1a482910da174b629f44e7dd8cfcd6924a4ef81a/scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518", size = 8651241, upload-time = "2025-07-18T08:01:13.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/92/85ec172418f39474c1cd0221d611345d4f433fc4ee2fc68e01f524ccc4e4/scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8", size = 9718677, upload-time = "2025-07-18T08:01:15.649Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/ce/abdb1dcbb1d2b66168ec43b23ee0cee356b4cc4100ddee3943934ebf1480/scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7", size = 9511189, upload-time = "2025-07-18T08:01:18.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/3b/47b5eaee01ef2b5a80ba3f7f6ecf79587cb458690857d4777bfd77371c6f/scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650", size = 8914794, upload-time = "2025-07-18T08:01:20.357Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/16/57f176585b35ed865f51b04117947fe20f130f78940c6477b6d66279c9c2/scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087", size = 9260431, upload-time = "2025-07-18T08:01:22.77Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/4e/899317092f5efcab0e9bc929e3391341cec8fb0e816c4789686770024580/scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f", size = 8637191, upload-time = "2025-07-18T08:01:24.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/1b/998312db6d361ded1dd56b457ada371a8d8d77ca2195a7d18fd8a1736f21/scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87", size = 9486346, upload-time = "2025-07-18T08:01:26.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/09/a2aa0b4e644e5c4ede7006748f24e72863ba2ae71897fecfd832afea01b4/scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7", size = 9290988, upload-time = "2025-07-18T08:01:28.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/fa/c61a787e35f05f17fc10523f567677ec4eeee5f95aa4798dbbbcd9625617/scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88", size = 8735568, upload-time = "2025-07-18T08:01:30.936Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143, upload-time = "2025-07-18T08:01:32.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977, upload-time = "2025-07-18T08:01:34.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142, upload-time = "2025-07-18T08:01:37.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996, upload-time = "2025-07-18T08:01:39.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418, upload-time = "2025-07-18T08:01:42.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466, upload-time = "2025-07-18T08:01:44.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467, upload-time = "2025-07-18T08:01:46.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052, upload-time = "2025-07-18T08:01:48.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575, upload-time = "2025-07-18T08:01:50.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310, upload-time = "2025-07-18T08:01:52.547Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scipy"
|
||||
version = "1.16.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/91/812adc6f74409b461e3a5fa97f4f74c769016919203138a3bf6fc24ba4c5/scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", size = 36552519, upload-time = "2025-07-27T16:26:29.658Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/18/8e355edcf3b71418d9e9f9acd2708cc3a6c27e8f98fde0ac34b8a0b45407/scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", size = 28638010, upload-time = "2025-07-27T16:26:38.196Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/eb/e931853058607bdfbc11b86df19ae7a08686121c203483f62f1ecae5989c/scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", size = 20909790, upload-time = "2025-07-27T16:26:43.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/0c/be83a271d6e96750cd0be2e000f35ff18880a46f05ce8b5d3465dc0f7a2a/scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", size = 23513352, upload-time = "2025-07-27T16:26:50.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/bf/fe6eb47e74f762f933cca962db7f2c7183acfdc4483bd1c3813cfe83e538/scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", size = 33534643, upload-time = "2025-07-27T16:26:57.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/ba/63f402e74875486b87ec6506a4f93f6d8a0d94d10467280f3d9d7837ce3a/scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", size = 35376776, upload-time = "2025-07-27T16:27:06.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/b4/04eb9d39ec26a1b939689102da23d505ea16cdae3dbb18ffc53d1f831044/scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", size = 35698906, upload-time = "2025-07-27T16:27:14.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/d6/bb5468da53321baeb001f6e4e0d9049eadd175a4a497709939128556e3ec/scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", size = 38129275, upload-time = "2025-07-27T16:27:23.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/94/994369978509f227cba7dfb9e623254d0d5559506fe994aef4bea3ed469c/scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", size = 38644572, upload-time = "2025-07-27T16:27:32.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/d9/ec4864f5896232133f51382b54a08de91a9d1af7a76dfa372894026dfee2/scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", size = 36575194, upload-time = "2025-07-27T16:27:41.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/6d/40e81ecfb688e9d25d34a847dca361982a6addf8e31f0957b1a54fbfa994/scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", size = 28594590, upload-time = "2025-07-27T16:27:49.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/37/9f65178edfcc629377ce9a64fc09baebea18c80a9e57ae09a52edf84880b/scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", size = 20866458, upload-time = "2025-07-27T16:27:54.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/7b/749a66766871ea4cb1d1ea10f27004db63023074c22abed51f22f09770e0/scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", size = 23539318, upload-time = "2025-07-27T16:28:01.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/db/8d4afec60eb833a666434d4541a3151eedbf2494ea6d4d468cbe877f00cd/scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", size = 33292899, upload-time = "2025-07-27T16:28:09.147Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", size = 35162637, upload-time = "2025-07-27T16:28:17.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/49/0648665f9c29fdaca4c679182eb972935b3b4f5ace41d323c32352f29816/scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", size = 35490507, upload-time = "2025-07-27T16:28:25.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/8f/66cbb9d6bbb18d8c658f774904f42a92078707a7c71e5347e8bf2f52bb89/scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", size = 37923998, upload-time = "2025-07-27T16:28:34.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/c3/61f273ae550fbf1667675701112e380881905e28448c080b23b5a181df7c/scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", size = 38508060, upload-time = "2025-07-27T16:28:43.242Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921, upload-time = "2025-07-27T16:30:30.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152, upload-time = "2025-07-27T16:30:35.336Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028, upload-time = "2025-07-27T16:30:39.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666, upload-time = "2025-07-27T16:30:43.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318, upload-time = "2025-07-27T16:30:48.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724, upload-time = "2025-07-27T16:30:54.26Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335, upload-time = "2025-07-27T16:30:59.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310, upload-time = "2025-07-27T16:31:06.151Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239, upload-time = "2025-07-27T16:31:59.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460, upload-time = "2025-07-27T16:31:11.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322, upload-time = "2025-07-27T16:31:17.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329, upload-time = "2025-07-27T16:31:21.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544, upload-time = "2025-07-27T16:31:25.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112, upload-time = "2025-07-27T16:31:30.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594, upload-time = "2025-07-27T16:31:36.112Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080, upload-time = "2025-07-27T16:31:42.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306, upload-time = "2025-07-27T16:31:48.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "80.9.0"
|
||||
|
|
@ -2910,27 +2374,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/d2/05/e6600db80270777c4a64238a98d442f0fd07cc8915be2a1c16da7f2b9e74/sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5", size = 5742435, upload-time = "2023-05-10T18:22:14.76Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threadpoolctl"
|
||||
version = "3.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/8a/c05f7831beb32aff70f808766224f11c650f7edfd49b27a8fc6666107006/threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355", size = 36266, upload-time = "2023-07-13T14:53:53.299Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/12/fd4dea011af9d69e1cad05c75f3f7202cdcbeac9b712eea58ca779a72865/threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032", size = 15539, upload-time = "2023-07-13T14:53:39.336Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tifffile"
|
||||
version = "2023.12.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/6c0eadea1ccfcda27e6cce400c366098b5b082138a073f4252fe399f4148/tifffile-2023.12.9.tar.gz", hash = "sha256:9dd1da91180a6453018a241ff219e1905f169384355cd89c9ef4034c1b46cdb8", size = 353467, upload-time = "2023-12-09T20:46:29.203Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/a4/569fc717831969cf48bced350bdaf070cdeab06918d179429899e144358d/tifffile-2023.12.9-py3-none-any.whl", hash = "sha256:9b066e4b1a900891ea42ffd33dab8ba34c537935618b9893ddef42d7d422692f", size = 223627, upload-time = "2023-12-09T20:46:26.569Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokenizers"
|
||||
version = "0.23.1"
|
||||
|
|
@ -3211,15 +2654,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/5a/a5/7aba9435beb863c2490bae3173a45f42044ac7a48155d3dd42ab49cfae45/watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", size = 268026, upload-time = "2023-10-13T13:05:38.591Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websocket-client"
|
||||
version = "1.8.0"
|
||||
|
|
|
|||
1
mobile/.gitignore
vendored
1
mobile/.gitignore
vendored
|
|
@ -32,6 +32,7 @@
|
|||
.pub/
|
||||
/build/
|
||||
lib/**/*.drift.dart
|
||||
lib/**/*.freezed.dart
|
||||
lib/routing/router.gr.dart
|
||||
test/drift/main/generated/
|
||||
|
||||
|
|
|
|||
|
|
@ -110,3 +110,17 @@ dart_code_metrics:
|
|||
- prefer-const-border-radius
|
||||
- prefer-declaring-const-constructor
|
||||
- prefer-switch-expression
|
||||
|
||||
# Riverpod
|
||||
- avoid-calling-notifier-members-inside-build
|
||||
- avoid-notifier-constructors
|
||||
- avoid-nullable-async-value-pattern
|
||||
- avoid-public-notifier-properties
|
||||
- avoid-ref-inside-state-dispose
|
||||
- avoid-ref-read-inside-build
|
||||
- avoid-ref-watch-outside-build
|
||||
- avoid-unnecessary-consumer-widgets
|
||||
- dispose-provided-instances
|
||||
- prefer-immutable-provider-arguments
|
||||
- use-ref-and-state-synchronously
|
||||
- use-ref-read-synchronously
|
||||
|
|
|
|||
|
|
@ -152,12 +152,6 @@
|
|||
This is used by the Flutter tool to
|
||||
generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||
<!-- Disables default WorkManager initialization to use our custom initialization -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
tools:node="remove" />
|
||||
|
||||
|
||||
<!-- Widgets -->
|
||||
<receiver
|
||||
|
|
|
|||
|
|
@ -3,17 +3,12 @@ package app.alextran.immich
|
|||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import app.alextran.immich.background.BackgroundEngineLock
|
||||
import app.alextran.immich.background.BackgroundWorkerApiImpl
|
||||
|
||||
class ImmichApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
val config = Configuration.Builder().build()
|
||||
WorkManager.initialize(this, config)
|
||||
// always start BackupWorker after WorkManager init; this fixes the following bug:
|
||||
// After the process is killed (by user or system), the first trigger (taking a new picture) is lost.
|
||||
// Thus, the BackupWorker is not started. If the system kills the process after each initialization
|
||||
// (because of low memory etc.), the backup is never performed.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
|||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.ext.SdkExtensions
|
||||
|
|
@ -30,6 +29,8 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
|
|
@ -377,7 +378,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
|||
)?.use { cursor -> cursor.count.toLong() } ?: 0L
|
||||
|
||||
|
||||
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result<List<PlatformAsset>>) -> Unit) {
|
||||
fun getAssetsForAlbum(
|
||||
albumId: String,
|
||||
updatedTimeCond: Long?,
|
||||
callback: (Result<List<PlatformAsset>>) -> Unit
|
||||
) {
|
||||
runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) }
|
||||
}
|
||||
|
||||
|
|
@ -419,7 +424,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
|||
}.awaitAll()
|
||||
|
||||
completeWhenActive(callback, Result.success(results))
|
||||
} catch (e: CancellationException) {
|
||||
} catch (_: CancellationException) {
|
||||
completeWhenActive(
|
||||
callback, Result.failure(
|
||||
FlutterError(
|
||||
|
|
@ -437,23 +442,20 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
|||
|
||||
private suspend fun hashAsset(assetId: String): HashResult {
|
||||
return try {
|
||||
val assetUri = ContentUris.withAppendedId(
|
||||
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
||||
assetId.toLong()
|
||||
)
|
||||
|
||||
val digest = MessageDigest.getInstance("SHA-1")
|
||||
ctx.contentResolver.openInputStream(assetUri)?.use { inputStream ->
|
||||
var bytesRead: Int
|
||||
openOriginalStream(assetId).use { inputStream ->
|
||||
val buffer = ByteArray(HASH_BUFFER_SIZE)
|
||||
while (inputStream.read(buffer).also { bytesRead = it } > 0) {
|
||||
while (true) {
|
||||
val bytesRead = inputStream.read(buffer)
|
||||
if (bytesRead == -1) break
|
||||
currentCoroutineContext().ensureActive()
|
||||
digest.update(buffer, 0, bytesRead)
|
||||
}
|
||||
} ?: return HashResult(assetId, "Cannot open input stream for asset", null)
|
||||
}
|
||||
|
||||
val hashString = Base64.encodeToString(digest.digest(), Base64.NO_WRAP)
|
||||
HashResult(assetId, null, hashString)
|
||||
HashResult(assetId, null, Base64.encodeToString(digest.digest(), Base64.NO_WRAP))
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: SecurityException) {
|
||||
HashResult(assetId, "Permission denied accessing asset: ${e.message}", null)
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -461,6 +463,37 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
|||
}
|
||||
}
|
||||
|
||||
private fun openOriginalStream(assetId: String): InputStream {
|
||||
val id = assetId.toLong()
|
||||
val collection = when (getMediaType(id)) {
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||
}
|
||||
val uri = ContentUris.withAppendedId(collection, id)
|
||||
val original = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
MediaStore.setRequireOriginal(uri)
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
|
||||
return ctx.contentResolver.openInputStream(original)
|
||||
?: throw IOException("Cannot open original stream for asset $assetId")
|
||||
}
|
||||
|
||||
private fun getMediaType(id: Long): Int =
|
||||
getCursor(
|
||||
MediaStore.VOLUME_EXTERNAL,
|
||||
"${MediaStore.MediaColumns._ID} = ?",
|
||||
arrayOf(id.toString()),
|
||||
arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE)
|
||||
)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE))
|
||||
} else {
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
|
||||
}
|
||||
} ?: MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
|
||||
|
||||
fun cancelHashing() {
|
||||
hashTask?.cancel()
|
||||
hashTask = null
|
||||
|
|
@ -476,8 +509,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
|||
syncJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
completeWhenActive(callback, Result.success(work()))
|
||||
} catch (e: CancellationException) {
|
||||
completeWhenActive(callback, Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null)))
|
||||
} catch (_: CancellationException) {
|
||||
completeWhenActive(
|
||||
callback,
|
||||
Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null))
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
completeWhenActive(callback, Result.failure(e))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class LocalImageApiImpl: LocalImageApi {
|
|||
requestOptions.version = .current
|
||||
return requestOptions
|
||||
}()
|
||||
private static let maxPixelSize: CGFloat = 16384
|
||||
|
||||
private static let registry = RequestRegistry<ImageRequest>()
|
||||
|
||||
|
|
@ -108,11 +109,16 @@ class LocalImageApiImpl: LocalImageApi {
|
|||
]))
|
||||
}
|
||||
|
||||
let isOriginal = !(width > 0 && height > 0)
|
||||
let targetSize = isOriginal
|
||||
? CGSize(width: Self.maxPixelSize, height: Self.maxPixelSize)
|
||||
: CGSize(width: Double(width), height: Double(height))
|
||||
let contentMode: PHImageContentMode = isOriginal ? .aspectFit : .aspectFill
|
||||
var image: UIImage?
|
||||
Self.imageManager.requestImage(
|
||||
for: asset,
|
||||
targetSize: width > 0 && height > 0 ? CGSize(width: Double(width), height: Double(height)) : PHImageManagerMaximumSize,
|
||||
contentMode: .aspectFill,
|
||||
targetSize: targetSize,
|
||||
contentMode: contentMode,
|
||||
options: Self.requestOptions,
|
||||
resultHandler: { (_image, info) -> Void in
|
||||
image = _image
|
||||
|
|
@ -123,12 +129,38 @@ class LocalImageApiImpl: LocalImageApi {
|
|||
return request.completion(ImageProcessing.cancelledResult)
|
||||
}
|
||||
|
||||
guard let image = image,
|
||||
let cgImage = image.cgImage else {
|
||||
guard let fastImage = image,
|
||||
var cgImage = fastImage.cgImage else {
|
||||
Self.registry.remove(requestId: requestId)
|
||||
return request.completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil)))
|
||||
}
|
||||
|
||||
// .fast can return larger than the target, so retry with .exact to guarantee the bound.
|
||||
if max(cgImage.width, cgImage.height) > Int(Self.maxPixelSize) {
|
||||
let exactOptions = Self.requestOptions.copy() as! PHImageRequestOptions
|
||||
exactOptions.resizeMode = .exact
|
||||
image = nil
|
||||
Self.imageManager.requestImage(
|
||||
for: asset,
|
||||
targetSize: targetSize,
|
||||
contentMode: contentMode,
|
||||
options: exactOptions,
|
||||
resultHandler: { (_image, info) -> Void in
|
||||
image = _image
|
||||
}
|
||||
)
|
||||
|
||||
if request.isCancelled {
|
||||
return request.completion(ImageProcessing.cancelledResult)
|
||||
}
|
||||
|
||||
guard let exactImage = image?.cgImage else {
|
||||
Self.registry.remove(requestId: requestId)
|
||||
return request.completion(.failure(PigeonError(code: "", message: "Could not resize image for \(assetId)", details: nil)))
|
||||
}
|
||||
cgImage = exactImage
|
||||
}
|
||||
|
||||
if request.isCancelled {
|
||||
return request.completion(ImageProcessing.cancelledResult)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,15 @@ struct ImageEntry: TimelineEntry {
|
|||
var subtitle: String? = nil
|
||||
var error: WidgetError? = nil
|
||||
var deepLink: URL? = nil
|
||||
var forceFullColor: Bool = true
|
||||
}
|
||||
|
||||
static func build(
|
||||
api: ImmichAPI,
|
||||
asset: Asset,
|
||||
dateOffset: Int,
|
||||
subtitle: String? = nil
|
||||
subtitle: String? = nil,
|
||||
forceFullColor: Bool = true
|
||||
)
|
||||
async throws -> Self
|
||||
{
|
||||
|
|
@ -34,7 +36,8 @@ struct ImageEntry: TimelineEntry {
|
|||
image: image,
|
||||
metadata: EntryMetadata(
|
||||
subtitle: subtitle,
|
||||
deepLink: asset.deepLink
|
||||
deepLink: asset.deepLink,
|
||||
forceFullColor: forceFullColor
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -111,12 +114,26 @@ struct ImageEntry: TimelineEntry {
|
|||
|
||||
}
|
||||
|
||||
// Required so that we can decode older versions of the metadata
|
||||
extension ImageEntry.Metadata {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle)
|
||||
error = try container.decodeIfPresent(WidgetError.self, forKey: .error)
|
||||
deepLink = try container.decodeIfPresent(URL.self, forKey: .deepLink)
|
||||
|
||||
forceFullColor =
|
||||
try container.decodeIfPresent(Bool.self, forKey: .forceFullColor) ?? true
|
||||
}
|
||||
}
|
||||
|
||||
func generateRandomEntries(
|
||||
api: ImmichAPI,
|
||||
now: Date,
|
||||
count: Int,
|
||||
filter: SearchFilter = Album.NONE.filter,
|
||||
subtitle: String? = nil
|
||||
subtitle: String? = nil,
|
||||
forceFullColor: Bool = true
|
||||
)
|
||||
async throws -> [ImageEntry]
|
||||
{
|
||||
|
|
@ -132,7 +149,8 @@ func generateRandomEntries(
|
|||
api: api,
|
||||
asset: asset,
|
||||
dateOffset: dateOffset,
|
||||
subtitle: subtitle
|
||||
subtitle: subtitle,
|
||||
forceFullColor: forceFullColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import WidgetKit
|
|||
|
||||
extension Image {
|
||||
@ViewBuilder
|
||||
func tintedWidgetImageModifier() -> some View {
|
||||
func tintedWidgetImageModifier(forceFullColor: Bool) -> some View {
|
||||
if #available(iOS 18.0, *) {
|
||||
self
|
||||
.widgetAccentedRenderingMode(.accentedDesaturated)
|
||||
.widgetAccentedRenderingMode(forceFullColor ? .accentedDesaturated : .fullColor)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
|
|
@ -18,15 +18,24 @@ struct ImmichWidgetView: View {
|
|||
|
||||
var body: some View {
|
||||
if let image = entry.image {
|
||||
ImmichWidgetContentView(image: image, subtitle: entry.metadata.subtitle, deepLink: entry.metadata.deepLink)
|
||||
ImmichWidgetContentView(
|
||||
image: image,
|
||||
subtitle: entry.metadata.subtitle,
|
||||
deepLink: entry.metadata.deepLink,
|
||||
forceFullColor: entry.metadata.forceFullColor
|
||||
)
|
||||
} else {
|
||||
ImmichWidgetLoadingView(message: entry.metadata.error?.errorDescription)
|
||||
ImmichWidgetLoadingView(
|
||||
message: entry.metadata.error?.errorDescription,
|
||||
forceFullColor: entry.metadata.forceFullColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ImmichWidgetLoadingView: View {
|
||||
let message: String?
|
||||
let forceFullColor: Bool
|
||||
|
||||
var body: some View {
|
||||
let messageText = Text(message ?? "")
|
||||
|
|
@ -39,7 +48,7 @@ private struct ImmichWidgetLoadingView: View {
|
|||
messageText.hidden()
|
||||
|
||||
Image("LaunchImage")
|
||||
.tintedWidgetImageModifier()
|
||||
.tintedWidgetImageModifier(forceFullColor: forceFullColor)
|
||||
|
||||
messageText
|
||||
}
|
||||
|
|
@ -50,13 +59,14 @@ private struct ImmichWidgetContentView: View {
|
|||
let image: UIImage
|
||||
let subtitle: String?
|
||||
let deepLink: URL?
|
||||
let forceFullColor: Bool
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Color.clear.overlay(
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.tintedWidgetImageModifier()
|
||||
.tintedWidgetImageModifier(forceFullColor: forceFullColor)
|
||||
.scaledToFill()
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,21 @@ import AppIntents
|
|||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
struct ImmichMemoryProvider: TimelineProvider {
|
||||
// MARK: Widget Configuration
|
||||
|
||||
struct MemoryConfigurationAppIntent: WidgetConfigurationIntent {
|
||||
static var title: LocalizedStringResource { "Memories" }
|
||||
static var description: IntentDescription {
|
||||
"See memories from Immich."
|
||||
}
|
||||
|
||||
@Parameter(title: "Always Display in Full Color", default: false)
|
||||
var forceFullColor: Bool
|
||||
}
|
||||
|
||||
// MARK: Provider
|
||||
|
||||
struct ImmichMemoryProvider: AppIntentTimelineProvider {
|
||||
func getYearDifferenceSubtitle(assetYear: Int) -> String {
|
||||
let currentYear = Calendar.current.component(.year, from: Date.now)
|
||||
// construct a "X years ago" subtitle
|
||||
|
|
@ -15,144 +29,132 @@ struct ImmichMemoryProvider: TimelineProvider {
|
|||
ImageEntry(date: Date(), image: nil)
|
||||
}
|
||||
|
||||
func getSnapshot(
|
||||
in context: Context,
|
||||
completion: @escaping @Sendable (ImageEntry) -> Void
|
||||
) {
|
||||
func snapshot(
|
||||
for configuration: MemoryConfigurationAppIntent,
|
||||
in context: Context
|
||||
) async -> ImageEntry {
|
||||
let cacheKey = "memory_\(context.family.rawValue)"
|
||||
|
||||
Task {
|
||||
guard let api = try? await ImmichAPI() else {
|
||||
completion(
|
||||
ImageEntry.handleError(for: cacheKey, error: .noLogin).entries.first!
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
guard let memories = try? await api.fetchMemory(for: Date.now)
|
||||
else {
|
||||
completion(ImageEntry.handleError(for: cacheKey).entries.first!)
|
||||
return
|
||||
}
|
||||
|
||||
for memory in memories {
|
||||
if let asset = memory.assets.first(where: { $0.type == .image }),
|
||||
let entry = try? await ImageEntry.build(
|
||||
api: api,
|
||||
asset: asset,
|
||||
dateOffset: 0,
|
||||
subtitle: getYearDifferenceSubtitle(assetYear: memory.data.year)
|
||||
)
|
||||
{
|
||||
completion(entry)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to random image
|
||||
guard
|
||||
let randomImage = try? await api.fetchSearchResults().first,
|
||||
let imageEntry = try? await ImageEntry.build(
|
||||
api: api,
|
||||
asset: randomImage,
|
||||
dateOffset: 0
|
||||
)
|
||||
else {
|
||||
completion(ImageEntry.handleError(for: cacheKey).entries.first!)
|
||||
return
|
||||
}
|
||||
|
||||
completion(imageEntry)
|
||||
guard let api = try? await ImmichAPI() else {
|
||||
return ImageEntry.handleError(for: cacheKey, error: .noLogin).entries
|
||||
.first!
|
||||
}
|
||||
|
||||
guard let memories = try? await api.fetchMemory(for: Date.now) else {
|
||||
return ImageEntry.handleError(for: cacheKey).entries.first!
|
||||
}
|
||||
|
||||
for memory in memories {
|
||||
if let asset = memory.assets.first(where: { $0.type == .image }),
|
||||
let entry = try? await ImageEntry.build(
|
||||
api: api,
|
||||
asset: asset,
|
||||
dateOffset: 0,
|
||||
subtitle: getYearDifferenceSubtitle(assetYear: memory.data.year),
|
||||
forceFullColor: configuration.forceFullColor
|
||||
)
|
||||
{
|
||||
return entry
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to random image
|
||||
guard
|
||||
let randomImage = try? await api.fetchSearchResults().first,
|
||||
let imageEntry = try? await ImageEntry.build(
|
||||
api: api,
|
||||
asset: randomImage,
|
||||
dateOffset: 0,
|
||||
forceFullColor: configuration.forceFullColor
|
||||
)
|
||||
else {
|
||||
return ImageEntry.handleError(for: cacheKey).entries.first!
|
||||
}
|
||||
|
||||
return imageEntry
|
||||
}
|
||||
|
||||
func getTimeline(
|
||||
in context: Context,
|
||||
completion: @escaping @Sendable (Timeline<ImageEntry>) -> Void
|
||||
) {
|
||||
Task {
|
||||
var entries: [ImageEntry] = []
|
||||
let now = Date()
|
||||
func timeline(
|
||||
for configuration: MemoryConfigurationAppIntent,
|
||||
in context: Context
|
||||
) async
|
||||
-> Timeline<ImageEntry>
|
||||
{
|
||||
var entries: [ImageEntry] = []
|
||||
let now = Date()
|
||||
|
||||
let cacheKey = "memory_\(context.family.rawValue)"
|
||||
let cacheKey = "memory_\(context.family.rawValue)"
|
||||
|
||||
guard let api = try? await ImmichAPI() else {
|
||||
completion(
|
||||
ImageEntry.handleError(for: cacheKey, error: .noLogin)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let memories: [MemoryResult]
|
||||
do {
|
||||
memories = try await api.fetchMemory(for: Date.now)
|
||||
|
||||
await withTaskGroup(of: ImageEntry?.self) { group in
|
||||
var totalAssets = 0
|
||||
|
||||
for memory in memories {
|
||||
for asset in memory.assets {
|
||||
if asset.type == .image && totalAssets < 12 {
|
||||
group.addTask {
|
||||
try? await ImageEntry.build(
|
||||
api: api,
|
||||
asset: asset,
|
||||
dateOffset: totalAssets,
|
||||
subtitle: getYearDifferenceSubtitle(
|
||||
assetYear: memory.data.year
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
totalAssets += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for await result in group {
|
||||
if let entry = result {
|
||||
entries.append(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
completion(ImageEntry.handleError(for: cacheKey))
|
||||
return
|
||||
}
|
||||
|
||||
// If we didn't add any memory images (some failure occurred or no images in memory),
|
||||
// default to 12 hours of random photos
|
||||
if entries.count == 0 {
|
||||
// this must be a do/catch since we need to
|
||||
// distinguish between a network fail and an empty search
|
||||
do {
|
||||
let search = try await generateRandomEntries(
|
||||
api: api,
|
||||
now: now,
|
||||
count: 12
|
||||
)
|
||||
|
||||
// Load or save a cached asset for when network conditions are bad
|
||||
if search.count == 0 {
|
||||
completion(
|
||||
ImageEntry.handleError(for: cacheKey, error: .noAssetsAvailable)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
entries.append(contentsOf: search)
|
||||
} catch {
|
||||
completion(ImageEntry.handleError(for: cacheKey))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// cache the last image
|
||||
try? entries.last!.cache(for: cacheKey)
|
||||
|
||||
completion(Timeline(entries: entries, policy: .atEnd))
|
||||
guard let api = try? await ImmichAPI() else {
|
||||
return ImageEntry.handleError(for: cacheKey, error: .noLogin)
|
||||
}
|
||||
|
||||
let memories: [MemoryResult]
|
||||
do {
|
||||
memories = try await api.fetchMemory(for: Date.now)
|
||||
|
||||
await withTaskGroup(of: ImageEntry?.self) { group in
|
||||
var totalAssets = 0
|
||||
|
||||
for memory in memories {
|
||||
for asset in memory.assets {
|
||||
if asset.type == .image && totalAssets < 12 {
|
||||
group.addTask {
|
||||
try? await ImageEntry.build(
|
||||
api: api,
|
||||
asset: asset,
|
||||
dateOffset: totalAssets,
|
||||
subtitle: getYearDifferenceSubtitle(
|
||||
assetYear: memory.data.year
|
||||
),
|
||||
forceFullColor: configuration.forceFullColor
|
||||
)
|
||||
}
|
||||
|
||||
totalAssets += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for await result in group {
|
||||
if let entry = result {
|
||||
entries.append(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
return ImageEntry.handleError(for: cacheKey)
|
||||
}
|
||||
|
||||
// If we didn't add any memory images (some failure occurred or no images in memory),
|
||||
// default to 12 hours of random photos
|
||||
if entries.count == 0 {
|
||||
// this must be a do/catch since we need to
|
||||
// distinguish between a network fail and an empty search
|
||||
do {
|
||||
let search = try await generateRandomEntries(
|
||||
api: api,
|
||||
now: now,
|
||||
count: 12,
|
||||
forceFullColor: configuration.forceFullColor
|
||||
)
|
||||
|
||||
// Load or save a cached asset for when network conditions are bad
|
||||
if search.count == 0 {
|
||||
return ImageEntry.handleError(for: cacheKey, error: .noAssetsAvailable)
|
||||
}
|
||||
|
||||
entries.append(contentsOf: search)
|
||||
} catch {
|
||||
return ImageEntry.handleError(for: cacheKey)
|
||||
}
|
||||
}
|
||||
|
||||
// cache the last image
|
||||
try? entries.last!.cache(for: cacheKey)
|
||||
|
||||
return Timeline(entries: entries, policy: .atEnd)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,8 +162,9 @@ struct ImmichMemoryWidget: Widget {
|
|||
let kind: String = "com.immich.widget.memory"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(
|
||||
AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: MemoryConfigurationAppIntent.self,
|
||||
provider: ImmichMemoryProvider()
|
||||
) { entry in
|
||||
ImmichWidgetView(entry: entry)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ struct RandomConfigurationAppIntent: WidgetConfigurationIntent {
|
|||
|
||||
@Parameter(title: "Show Album Name", default: false)
|
||||
var showAlbumName: Bool
|
||||
|
||||
@Parameter(title: "Always Display in Full Color", default: false)
|
||||
var forceFullColor: Bool
|
||||
}
|
||||
|
||||
// MARK: Provider
|
||||
|
|
@ -76,7 +79,8 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
|
|||
let entry = try? await ImageEntry.build(
|
||||
api: api,
|
||||
asset: randomImage,
|
||||
dateOffset: 0
|
||||
dateOffset: 0,
|
||||
forceFullColor: configuration.forceFullColor
|
||||
)
|
||||
else {
|
||||
return ImageEntry.handleError(for: cacheKey).entries.first!
|
||||
|
|
@ -114,7 +118,8 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
|
|||
now: now,
|
||||
count: 12,
|
||||
filter: album.filter,
|
||||
subtitle: configuration.showAlbumName ? albumName : nil
|
||||
subtitle: configuration.showAlbumName ? albumName : nil,
|
||||
forceFullColor: configuration.forceFullColor
|
||||
)
|
||||
|
||||
// Load or save a cached asset for when network conditions are bad
|
||||
|
|
|
|||
|
|
@ -24,3 +24,14 @@ enum SlideshowLook { contain, cover, blurredBackground }
|
|||
enum SlideshowDirection { forward, backward, shuffle }
|
||||
|
||||
enum PartnerDirection { sharedBy, sharedWith }
|
||||
|
||||
enum DevicePermission { photos, videos, storage, mediaLocation }
|
||||
|
||||
enum DevicePermissionStatus {
|
||||
denied,
|
||||
granted,
|
||||
limited,
|
||||
permanentlyDenied;
|
||||
|
||||
bool get hasAccess => this == granted || this == limited;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'album.model.freezed.dart';
|
||||
|
||||
enum AlbumAssetOrder {
|
||||
// do not change this order!
|
||||
asc,
|
||||
|
|
@ -12,118 +17,20 @@ enum AlbumUserRole {
|
|||
}
|
||||
|
||||
// Model for an album stored in the server
|
||||
class RemoteAlbum {
|
||||
final String id;
|
||||
final String name;
|
||||
final String ownerId;
|
||||
final String description;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String? thumbnailAssetId;
|
||||
final bool isActivityEnabled;
|
||||
final AlbumAssetOrder order;
|
||||
final int assetCount;
|
||||
final String ownerName;
|
||||
final bool isShared;
|
||||
|
||||
const RemoteAlbum({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.ownerId,
|
||||
required this.description,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.thumbnailAssetId,
|
||||
required this.isActivityEnabled,
|
||||
required this.order,
|
||||
required this.assetCount,
|
||||
required this.ownerName,
|
||||
required this.isShared,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''Album {
|
||||
id: $id,
|
||||
name: $name,
|
||||
ownerId: $ownerId,
|
||||
description: $description,
|
||||
createdAt: $createdAt,
|
||||
updatedAt: $updatedAt,
|
||||
isActivityEnabled: $isActivityEnabled,
|
||||
order: $order,
|
||||
thumbnailAssetId: ${thumbnailAssetId ?? "<NA>"}
|
||||
assetCount: $assetCount
|
||||
ownerName: $ownerName
|
||||
isShared: $isShared
|
||||
}''';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is! RemoteAlbum) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return id == other.id &&
|
||||
name == other.name &&
|
||||
ownerId == other.ownerId &&
|
||||
description == other.description &&
|
||||
createdAt == other.createdAt &&
|
||||
updatedAt == other.updatedAt &&
|
||||
thumbnailAssetId == other.thumbnailAssetId &&
|
||||
isActivityEnabled == other.isActivityEnabled &&
|
||||
order == other.order &&
|
||||
assetCount == other.assetCount &&
|
||||
ownerName == other.ownerName &&
|
||||
isShared == other.isShared;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^
|
||||
name.hashCode ^
|
||||
ownerId.hashCode ^
|
||||
description.hashCode ^
|
||||
createdAt.hashCode ^
|
||||
updatedAt.hashCode ^
|
||||
thumbnailAssetId.hashCode ^
|
||||
isActivityEnabled.hashCode ^
|
||||
order.hashCode ^
|
||||
assetCount.hashCode ^
|
||||
ownerName.hashCode ^
|
||||
isShared.hashCode;
|
||||
}
|
||||
|
||||
RemoteAlbum copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? ownerId,
|
||||
String? description,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
@freezed
|
||||
abstract class RemoteAlbum with _$RemoteAlbum {
|
||||
const factory RemoteAlbum({
|
||||
required String id,
|
||||
required String name,
|
||||
required String ownerId,
|
||||
required String description,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
String? thumbnailAssetId,
|
||||
bool? isActivityEnabled,
|
||||
AlbumAssetOrder? order,
|
||||
int? assetCount,
|
||||
String? ownerName,
|
||||
bool? isShared,
|
||||
}) {
|
||||
return RemoteAlbum(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
description: description ?? this.description,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId,
|
||||
isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled,
|
||||
order: order ?? this.order,
|
||||
assetCount: assetCount ?? this.assetCount,
|
||||
ownerName: ownerName ?? this.ownerName,
|
||||
isShared: isShared ?? this.isShared,
|
||||
);
|
||||
}
|
||||
required bool isActivityEnabled,
|
||||
required AlbumAssetOrder order,
|
||||
required int assetCount,
|
||||
required String ownerName,
|
||||
required bool isShared,
|
||||
}) = _RemoteAlbum;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'local_album.model.freezed.dart';
|
||||
|
||||
enum BackupSelection {
|
||||
// Used to sort albums based on the backupSelection
|
||||
// selected -> none -> excluded
|
||||
|
|
@ -7,85 +12,15 @@ enum BackupSelection {
|
|||
excluded,
|
||||
}
|
||||
|
||||
class LocalAlbum {
|
||||
final String id;
|
||||
final String name;
|
||||
final DateTime updatedAt;
|
||||
final bool isIosSharedAlbum;
|
||||
|
||||
final int assetCount;
|
||||
final BackupSelection backupSelection;
|
||||
final String? linkedRemoteAlbumId;
|
||||
|
||||
const LocalAlbum({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.updatedAt,
|
||||
this.assetCount = 0,
|
||||
this.backupSelection = BackupSelection.none,
|
||||
this.isIosSharedAlbum = false,
|
||||
this.linkedRemoteAlbumId,
|
||||
});
|
||||
|
||||
LocalAlbum copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
DateTime? updatedAt,
|
||||
int? assetCount,
|
||||
BackupSelection? backupSelection,
|
||||
bool? isIosSharedAlbum,
|
||||
@freezed
|
||||
abstract class LocalAlbum with _$LocalAlbum {
|
||||
const factory LocalAlbum({
|
||||
required String id,
|
||||
required String name,
|
||||
required DateTime updatedAt,
|
||||
@Default(0) int assetCount,
|
||||
@Default(BackupSelection.none) BackupSelection backupSelection,
|
||||
@Default(false) bool isIosSharedAlbum,
|
||||
String? linkedRemoteAlbumId,
|
||||
}) {
|
||||
return LocalAlbum(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
assetCount: assetCount ?? this.assetCount,
|
||||
backupSelection: backupSelection ?? this.backupSelection,
|
||||
isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum,
|
||||
linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is! LocalAlbum) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.id == id &&
|
||||
other.name == name &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.assetCount == assetCount &&
|
||||
other.backupSelection == backupSelection &&
|
||||
other.isIosSharedAlbum == isIosSharedAlbum &&
|
||||
other.linkedRemoteAlbumId == linkedRemoteAlbumId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^
|
||||
name.hashCode ^
|
||||
updatedAt.hashCode ^
|
||||
assetCount.hashCode ^
|
||||
backupSelection.hashCode ^
|
||||
isIosSharedAlbum.hashCode ^
|
||||
linkedRemoteAlbumId.hashCode;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''LocalAlbum: {
|
||||
id: $id,
|
||||
name: $name,
|
||||
updatedAt: $updatedAt,
|
||||
assetCount: $assetCount,
|
||||
backupSelection: $backupSelection,
|
||||
isIosSharedAlbum: $isIosSharedAlbum
|
||||
linkedRemoteAlbumId: $linkedRemoteAlbumId,
|
||||
}''';
|
||||
}
|
||||
}) = _LocalAlbum;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ sealed class BaseAsset {
|
|||
|
||||
// Overridden in subclasses
|
||||
AssetState get storage;
|
||||
String get id;
|
||||
String? get localId;
|
||||
String? get remoteId;
|
||||
String get heroTag;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
part of 'base_asset.model.dart';
|
||||
|
||||
class LocalAsset extends BaseAsset {
|
||||
@override
|
||||
final String id;
|
||||
final String? remoteAssetId;
|
||||
final String? cloudId;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ enum AssetVisibility { timeline, hidden, archive, locked }
|
|||
|
||||
// Model for an asset stored in the server
|
||||
class RemoteAsset extends BaseAsset {
|
||||
@override
|
||||
final String id;
|
||||
final String? localAssetId;
|
||||
final String? thumbHash;
|
||||
|
|
|
|||
|
|
@ -1,100 +1,21 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'asset_face.model.freezed.dart';
|
||||
|
||||
// Model for an asset face stored in the server
|
||||
class AssetFace {
|
||||
final String id;
|
||||
final String assetId;
|
||||
final String? personId;
|
||||
final int imageWidth;
|
||||
final int imageHeight;
|
||||
final int boundingBoxX1;
|
||||
final int boundingBoxY1;
|
||||
final int boundingBoxX2;
|
||||
final int boundingBoxY2;
|
||||
final String sourceType;
|
||||
|
||||
const AssetFace({
|
||||
required this.id,
|
||||
required this.assetId,
|
||||
this.personId,
|
||||
required this.imageWidth,
|
||||
required this.imageHeight,
|
||||
required this.boundingBoxX1,
|
||||
required this.boundingBoxY1,
|
||||
required this.boundingBoxX2,
|
||||
required this.boundingBoxY2,
|
||||
required this.sourceType,
|
||||
});
|
||||
|
||||
AssetFace copyWith({
|
||||
String? id,
|
||||
String? assetId,
|
||||
@freezed
|
||||
abstract class AssetFace with _$AssetFace {
|
||||
const factory AssetFace({
|
||||
required String id,
|
||||
required String assetId,
|
||||
String? personId,
|
||||
int? imageWidth,
|
||||
int? imageHeight,
|
||||
int? boundingBoxX1,
|
||||
int? boundingBoxY1,
|
||||
int? boundingBoxX2,
|
||||
int? boundingBoxY2,
|
||||
String? sourceType,
|
||||
}) {
|
||||
return AssetFace(
|
||||
id: id ?? this.id,
|
||||
assetId: assetId ?? this.assetId,
|
||||
personId: personId ?? this.personId,
|
||||
imageWidth: imageWidth ?? this.imageWidth,
|
||||
imageHeight: imageHeight ?? this.imageHeight,
|
||||
boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1,
|
||||
boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1,
|
||||
boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2,
|
||||
boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2,
|
||||
sourceType: sourceType ?? this.sourceType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''AssetFace {
|
||||
id: $id,
|
||||
assetId: $assetId,
|
||||
personId: ${personId ?? "<NA>"},
|
||||
imageWidth: $imageWidth,
|
||||
imageHeight: $imageHeight,
|
||||
boundingBoxX1: $boundingBoxX1,
|
||||
boundingBoxY1: $boundingBoxY1,
|
||||
boundingBoxX2: $boundingBoxX2,
|
||||
boundingBoxY2: $boundingBoxY2,
|
||||
sourceType: $sourceType,
|
||||
}''';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant AssetFace other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.id == id &&
|
||||
other.assetId == assetId &&
|
||||
other.personId == personId &&
|
||||
other.imageWidth == imageWidth &&
|
||||
other.imageHeight == imageHeight &&
|
||||
other.boundingBoxX1 == boundingBoxX1 &&
|
||||
other.boundingBoxY1 == boundingBoxY1 &&
|
||||
other.boundingBoxX2 == boundingBoxX2 &&
|
||||
other.boundingBoxY2 == boundingBoxY2 &&
|
||||
other.sourceType == sourceType;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^
|
||||
assetId.hashCode ^
|
||||
personId.hashCode ^
|
||||
imageWidth.hashCode ^
|
||||
imageHeight.hashCode ^
|
||||
boundingBoxX1.hashCode ^
|
||||
boundingBoxY1.hashCode ^
|
||||
boundingBoxX2.hashCode ^
|
||||
boundingBoxY2.hashCode ^
|
||||
sourceType.hashCode;
|
||||
}
|
||||
required int imageWidth,
|
||||
required int imageHeight,
|
||||
required int boundingBoxX1,
|
||||
required int boundingBoxY1,
|
||||
required int boundingBoxX2,
|
||||
required int boundingBoxY2,
|
||||
required String sourceType,
|
||||
}) = _AssetFace;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,14 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
|
||||
class AlbumConfig {
|
||||
final AlbumSortMode sortMode;
|
||||
final bool isReverse;
|
||||
final bool isGrid;
|
||||
part 'album_config.freezed.dart';
|
||||
|
||||
const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false});
|
||||
|
||||
AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) => AlbumConfig(
|
||||
sortMode: sortMode ?? this.sortMode,
|
||||
isReverse: isReverse ?? this.isReverse,
|
||||
isGrid: isGrid ?? this.isGrid,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is AlbumConfig && other.sortMode == sortMode && other.isReverse == isReverse && other.isGrid == isGrid);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(sortMode, isReverse, isGrid);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumConfig(sortMode: $sortMode, isReverse: $isReverse, isGrid: $isGrid)';
|
||||
@freezed
|
||||
abstract class AlbumConfig with _$AlbumConfig {
|
||||
const factory AlbumConfig({
|
||||
@Default(AlbumSortMode.mostRecent) AlbumSortMode sortMode,
|
||||
@Default(true) bool isReverse,
|
||||
@Default(false) bool isGrid,
|
||||
}) = _AlbumConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/constants/colors.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/config/album_config.dart';
|
||||
|
|
@ -19,107 +21,29 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
import 'package:immich_mobile/utils/semver.dart';
|
||||
|
||||
part 'app_config.freezed.dart';
|
||||
|
||||
const defaultConfig = AppConfig();
|
||||
|
||||
class AppConfig {
|
||||
final LogLevel logLevel;
|
||||
final ThemeConfig theme;
|
||||
final CleanupConfig cleanup;
|
||||
final MapConfig map;
|
||||
final TimelineConfig timeline;
|
||||
final ImageConfig image;
|
||||
final ViewerConfig viewer;
|
||||
final SlideshowConfig slideshow;
|
||||
final AlbumConfig album;
|
||||
final BackupConfig backup;
|
||||
final NetworkConfig network;
|
||||
final ShareConfig share;
|
||||
final FeatureMessageConfig featureMessage;
|
||||
@freezed
|
||||
abstract class AppConfig with _$AppConfig {
|
||||
const AppConfig._();
|
||||
|
||||
const AppConfig({
|
||||
this.logLevel = .info,
|
||||
this.theme = const .new(),
|
||||
this.cleanup = const .new(),
|
||||
this.map = const .new(),
|
||||
this.timeline = const .new(),
|
||||
this.image = const .new(),
|
||||
this.viewer = const .new(),
|
||||
this.slideshow = const .new(),
|
||||
this.album = const .new(),
|
||||
this.backup = const .new(),
|
||||
this.network = const .new(),
|
||||
this.share = const .new(),
|
||||
this.featureMessage = const .new(),
|
||||
});
|
||||
|
||||
AppConfig copyWith({
|
||||
LogLevel? logLevel,
|
||||
ThemeConfig? theme,
|
||||
CleanupConfig? cleanup,
|
||||
MapConfig? map,
|
||||
TimelineConfig? timeline,
|
||||
ImageConfig? image,
|
||||
ViewerConfig? viewer,
|
||||
SlideshowConfig? slideshow,
|
||||
AlbumConfig? album,
|
||||
BackupConfig? backup,
|
||||
NetworkConfig? network,
|
||||
ShareConfig? share,
|
||||
FeatureMessageConfig? featureMessage,
|
||||
}) => .new(
|
||||
logLevel: logLevel ?? this.logLevel,
|
||||
theme: theme ?? this.theme,
|
||||
cleanup: cleanup ?? this.cleanup,
|
||||
map: map ?? this.map,
|
||||
timeline: timeline ?? this.timeline,
|
||||
image: image ?? this.image,
|
||||
viewer: viewer ?? this.viewer,
|
||||
slideshow: slideshow ?? this.slideshow,
|
||||
album: album ?? this.album,
|
||||
backup: backup ?? this.backup,
|
||||
network: network ?? this.network,
|
||||
share: share ?? this.share,
|
||||
featureMessage: featureMessage ?? this.featureMessage,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is AppConfig &&
|
||||
other.logLevel == logLevel &&
|
||||
other.theme == theme &&
|
||||
other.cleanup == cleanup &&
|
||||
other.map == map &&
|
||||
other.timeline == timeline &&
|
||||
other.image == image &&
|
||||
other.viewer == viewer &&
|
||||
other.slideshow == slideshow &&
|
||||
other.album == album &&
|
||||
other.backup == backup &&
|
||||
other.network == network &&
|
||||
other.share == share &&
|
||||
other.featureMessage == featureMessage);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
logLevel,
|
||||
theme,
|
||||
cleanup,
|
||||
map,
|
||||
timeline,
|
||||
image,
|
||||
viewer,
|
||||
slideshow,
|
||||
album,
|
||||
backup,
|
||||
network,
|
||||
share,
|
||||
featureMessage,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network, share: $share, featureMessage: $featureMessage)';
|
||||
const factory AppConfig({
|
||||
@Default(LogLevel.info) LogLevel logLevel,
|
||||
@Default(ThemeConfig()) ThemeConfig theme,
|
||||
@Default(CleanupConfig()) CleanupConfig cleanup,
|
||||
@Default(MapConfig()) MapConfig map,
|
||||
@Default(TimelineConfig()) TimelineConfig timeline,
|
||||
@Default(ImageConfig()) ImageConfig image,
|
||||
@Default(ViewerConfig()) ViewerConfig viewer,
|
||||
@Default(SlideshowConfig()) SlideshowConfig slideshow,
|
||||
@Default(AlbumConfig()) AlbumConfig album,
|
||||
@Default(BackupConfig()) BackupConfig backup,
|
||||
@Default(NetworkConfig()) NetworkConfig network,
|
||||
@Default(ShareConfig()) ShareConfig share,
|
||||
@Default(FeatureMessageConfig()) FeatureMessageConfig featureMessage,
|
||||
}) = _AppConfig;
|
||||
|
||||
T read<T>(SettingsKey<T> key) =>
|
||||
(switch (key) {
|
||||
|
|
|
|||
|
|
@ -1,52 +1,16 @@
|
|||
class BackupConfig {
|
||||
final bool enabled;
|
||||
final bool useCellularForVideos;
|
||||
final bool useCellularForPhotos;
|
||||
final bool requireCharging;
|
||||
final int triggerDelay;
|
||||
final bool syncAlbums;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
const BackupConfig({
|
||||
this.enabled = false,
|
||||
this.useCellularForVideos = false,
|
||||
this.useCellularForPhotos = false,
|
||||
this.requireCharging = false,
|
||||
this.triggerDelay = 30,
|
||||
this.syncAlbums = false,
|
||||
});
|
||||
part 'backup_config.freezed.dart';
|
||||
|
||||
BackupConfig copyWith({
|
||||
bool? enabled,
|
||||
bool? useCellularForVideos,
|
||||
bool? useCellularForPhotos,
|
||||
bool? requireCharging,
|
||||
int? triggerDelay,
|
||||
bool? syncAlbums,
|
||||
}) => BackupConfig(
|
||||
enabled: enabled ?? this.enabled,
|
||||
useCellularForVideos: useCellularForVideos ?? this.useCellularForVideos,
|
||||
useCellularForPhotos: useCellularForPhotos ?? this.useCellularForPhotos,
|
||||
requireCharging: requireCharging ?? this.requireCharging,
|
||||
triggerDelay: triggerDelay ?? this.triggerDelay,
|
||||
syncAlbums: syncAlbums ?? this.syncAlbums,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is BackupConfig &&
|
||||
other.enabled == enabled &&
|
||||
other.useCellularForVideos == useCellularForVideos &&
|
||||
other.useCellularForPhotos == useCellularForPhotos &&
|
||||
other.requireCharging == requireCharging &&
|
||||
other.triggerDelay == triggerDelay &&
|
||||
other.syncAlbums == syncAlbums);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(enabled, useCellularForVideos, useCellularForPhotos, requireCharging, triggerDelay, syncAlbums);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'BackupConfig(enabled: $enabled, useCellularForVideos: $useCellularForVideos, useCellularForPhotos: $useCellularForPhotos, requireCharging: $requireCharging, triggerDelay: $triggerDelay, syncAlbums: $syncAlbums)';
|
||||
@freezed
|
||||
abstract class BackupConfig with _$BackupConfig {
|
||||
const factory BackupConfig({
|
||||
@Default(false) bool enabled,
|
||||
@Default(false) bool useCellularForVideos,
|
||||
@Default(false) bool useCellularForPhotos,
|
||||
@Default(false) bool requireCharging,
|
||||
@Default(30) int triggerDelay,
|
||||
@Default(false) bool syncAlbums,
|
||||
}) = _BackupConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,11 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/utils/semver.dart';
|
||||
|
||||
class FeatureMessageConfig {
|
||||
final SemVer seenRelease;
|
||||
part 'feature_message_config.freezed.dart';
|
||||
|
||||
const FeatureMessageConfig({this.seenRelease = const SemVer(major: 0, minor: 0, patch: 0)});
|
||||
|
||||
FeatureMessageConfig copyWith({SemVer? seenRelease}) =>
|
||||
FeatureMessageConfig(seenRelease: seenRelease ?? this.seenRelease);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) || (other is FeatureMessageConfig && other.seenRelease == seenRelease);
|
||||
|
||||
@override
|
||||
int get hashCode => seenRelease.hashCode;
|
||||
|
||||
@override
|
||||
String toString() => 'FeatureMessageConfig(seenRelease: $seenRelease)';
|
||||
@freezed
|
||||
abstract class FeatureMessageConfig with _$FeatureMessageConfig {
|
||||
const factory FeatureMessageConfig({@Default(SemVer(major: 0, minor: 0, patch: 0)) SemVer seenRelease}) =
|
||||
_FeatureMessageConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,9 @@
|
|||
class ImageConfig {
|
||||
final bool preferRemote;
|
||||
final bool loadOriginal;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
const ImageConfig({this.preferRemote = false, this.loadOriginal = false});
|
||||
part 'image_config.freezed.dart';
|
||||
|
||||
ImageConfig copyWith({bool? preferRemote, bool? loadOriginal}) =>
|
||||
ImageConfig(preferRemote: preferRemote ?? this.preferRemote, loadOriginal: loadOriginal ?? this.loadOriginal);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is ImageConfig && other.preferRemote == preferRemote && other.loadOriginal == loadOriginal);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(preferRemote, loadOriginal);
|
||||
|
||||
@override
|
||||
String toString() => 'ImageConfig(preferRemoteImage: $preferRemote, loadOriginal: $loadOriginal)';
|
||||
@freezed
|
||||
abstract class ImageConfig with _$ImageConfig {
|
||||
const factory ImageConfig({@Default(false) bool preferRemote, @Default(false) bool loadOriginal}) = _ImageConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
|
||||
class ShareConfig {
|
||||
final ShareAssetType fileType;
|
||||
part 'share_config.freezed.dart';
|
||||
|
||||
const ShareConfig({this.fileType = ShareAssetType.original});
|
||||
|
||||
ShareConfig copyWith({ShareAssetType? fileType}) => ShareConfig(fileType: fileType ?? this.fileType);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || (other is ShareConfig && other.fileType == fileType);
|
||||
|
||||
@override
|
||||
int get hashCode => fileType.hashCode;
|
||||
|
||||
@override
|
||||
String toString() => 'ShareConfig(fileType: $fileType)';
|
||||
@freezed
|
||||
abstract class ShareConfig with _$ShareConfig {
|
||||
const factory ShareConfig({@Default(ShareAssetType.original) ShareAssetType fileType}) = _ShareConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,15 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
|
||||
class SlideshowConfig {
|
||||
final bool repeat;
|
||||
final int duration;
|
||||
final SlideshowLook look;
|
||||
final SlideshowDirection direction;
|
||||
part 'slideshow_config.freezed.dart';
|
||||
|
||||
const SlideshowConfig({
|
||||
this.repeat = true,
|
||||
this.duration = 5,
|
||||
this.look = SlideshowLook.blurredBackground,
|
||||
this.direction = SlideshowDirection.forward,
|
||||
});
|
||||
|
||||
SlideshowConfig copyWith({bool? repeat, int? duration, SlideshowLook? look, SlideshowDirection? direction}) =>
|
||||
SlideshowConfig(
|
||||
repeat: repeat ?? this.repeat,
|
||||
duration: duration ?? this.duration,
|
||||
look: look ?? this.look,
|
||||
direction: direction ?? this.direction,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is SlideshowConfig &&
|
||||
other.repeat == repeat &&
|
||||
other.duration == duration &&
|
||||
other.look == look &&
|
||||
other.direction == direction);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(repeat, duration, look, direction);
|
||||
|
||||
@override
|
||||
String toString() => 'SlideshowConfig(repeat: $repeat, duration: $duration, look: $look, direction: $direction)';
|
||||
@freezed
|
||||
abstract class SlideshowConfig with _$SlideshowConfig {
|
||||
const factory SlideshowConfig({
|
||||
@Default(true) bool repeat,
|
||||
@Default(5) int duration,
|
||||
@Default(SlideshowLook.blurredBackground) SlideshowLook look,
|
||||
@Default(SlideshowDirection.forward) SlideshowDirection direction,
|
||||
}) = _SlideshowConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,16 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/constants/colors.dart';
|
||||
|
||||
class ThemeConfig {
|
||||
final ThemeMode mode;
|
||||
final ImmichColorPreset primaryColor;
|
||||
final bool dynamicTheme;
|
||||
final bool colorfulInterface;
|
||||
part 'theme_config.freezed.dart';
|
||||
|
||||
const ThemeConfig({
|
||||
this.mode = .system,
|
||||
this.primaryColor = .indigo,
|
||||
this.dynamicTheme = false,
|
||||
this.colorfulInterface = true,
|
||||
});
|
||||
|
||||
ThemeConfig copyWith({
|
||||
ThemeMode? mode,
|
||||
ImmichColorPreset? primaryColor,
|
||||
bool? dynamicTheme,
|
||||
bool? colorfulInterface,
|
||||
}) => .new(
|
||||
mode: mode ?? this.mode,
|
||||
primaryColor: primaryColor ?? this.primaryColor,
|
||||
dynamicTheme: dynamicTheme ?? this.dynamicTheme,
|
||||
colorfulInterface: colorfulInterface ?? this.colorfulInterface,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is ThemeConfig &&
|
||||
other.mode == mode &&
|
||||
other.primaryColor == primaryColor &&
|
||||
other.dynamicTheme == dynamicTheme &&
|
||||
other.colorfulInterface == colorfulInterface);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(mode, primaryColor, dynamicTheme, colorfulInterface);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'ThemeConfig(mode: $mode, primaryColor: $primaryColor, dynamicTheme: $dynamicTheme, colorfulInterface: $colorfulInterface)';
|
||||
@freezed
|
||||
abstract class ThemeConfig with _$ThemeConfig {
|
||||
const factory ThemeConfig({
|
||||
@Default(ThemeMode.system) ThemeMode mode,
|
||||
@Default(ImmichColorPreset.indigo) ImmichColorPreset primaryColor,
|
||||
@Default(false) bool dynamicTheme,
|
||||
@Default(true) bool colorfulInterface,
|
||||
}) = _ThemeConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,14 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
|
||||
class TimelineConfig {
|
||||
final int tilesPerRow;
|
||||
final GroupAssetsBy groupAssetsBy;
|
||||
final bool storageIndicator;
|
||||
part 'timeline_config.freezed.dart';
|
||||
|
||||
const TimelineConfig({this.tilesPerRow = 4, this.groupAssetsBy = GroupAssetsBy.day, this.storageIndicator = true});
|
||||
|
||||
TimelineConfig copyWith({int? tilesPerRow, GroupAssetsBy? groupAssetsBy, bool? storageIndicator}) => TimelineConfig(
|
||||
tilesPerRow: tilesPerRow ?? this.tilesPerRow,
|
||||
groupAssetsBy: groupAssetsBy ?? this.groupAssetsBy,
|
||||
storageIndicator: storageIndicator ?? this.storageIndicator,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is TimelineConfig &&
|
||||
other.tilesPerRow == tilesPerRow &&
|
||||
other.groupAssetsBy == groupAssetsBy &&
|
||||
other.storageIndicator == storageIndicator);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(tilesPerRow, groupAssetsBy, storageIndicator);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'TimelineConfig(tilesPerRow: $tilesPerRow, groupAssetsBy: $groupAssetsBy, storageIndicator: $storageIndicator)';
|
||||
@freezed
|
||||
abstract class TimelineConfig with _$TimelineConfig {
|
||||
const factory TimelineConfig({
|
||||
@Default(4) int tilesPerRow,
|
||||
@Default(GroupAssetsBy.day) GroupAssetsBy groupAssetsBy,
|
||||
@Default(true) bool storageIndicator,
|
||||
}) = _TimelineConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,14 @@
|
|||
class ViewerConfig {
|
||||
final bool loopVideo;
|
||||
final bool loadOriginalVideo;
|
||||
final bool autoPlayVideo;
|
||||
final bool tapToNavigate;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
const ViewerConfig({
|
||||
this.loopVideo = true,
|
||||
this.loadOriginalVideo = false,
|
||||
this.autoPlayVideo = true,
|
||||
this.tapToNavigate = false,
|
||||
});
|
||||
part 'viewer_config.freezed.dart';
|
||||
|
||||
ViewerConfig copyWith({bool? loopVideo, bool? loadOriginalVideo, bool? autoPlayVideo, bool? tapToNavigate}) =>
|
||||
ViewerConfig(
|
||||
loopVideo: loopVideo ?? this.loopVideo,
|
||||
loadOriginalVideo: loadOriginalVideo ?? this.loadOriginalVideo,
|
||||
autoPlayVideo: autoPlayVideo ?? this.autoPlayVideo,
|
||||
tapToNavigate: tapToNavigate ?? this.tapToNavigate,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is ViewerConfig &&
|
||||
other.loopVideo == loopVideo &&
|
||||
other.loadOriginalVideo == loadOriginalVideo &&
|
||||
other.autoPlayVideo == autoPlayVideo &&
|
||||
other.tapToNavigate == tapToNavigate);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(loopVideo, loadOriginalVideo, autoPlayVideo, tapToNavigate);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'ViewerConfig(loopVideo: $loopVideo, loadOriginalVideo: $loadOriginalVideo, autoPlayVideo: $autoPlayVideo, tapToNavigate: $tapToNavigate)';
|
||||
@freezed
|
||||
abstract class ViewerConfig with _$ViewerConfig {
|
||||
const factory ViewerConfig({
|
||||
@Default(true) bool loopVideo,
|
||||
@Default(false) bool loadOriginalVideo,
|
||||
@Default(true) bool autoPlayVideo,
|
||||
@Default(false) bool tapToNavigate,
|
||||
}) = _ViewerConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,40 @@
|
|||
class ExifInfo {
|
||||
final int? assetId;
|
||||
final int? fileSize;
|
||||
final String? description;
|
||||
final bool isFlipped;
|
||||
final String? orientation;
|
||||
final String? timeZone;
|
||||
final DateTime? dateTimeOriginal;
|
||||
final int? rating;
|
||||
final int? width;
|
||||
final int? height;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
// GPS
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
final String? city;
|
||||
final String? state;
|
||||
final String? country;
|
||||
part 'exif.model.freezed.dart';
|
||||
|
||||
// Camera related
|
||||
final String? make;
|
||||
final String? model;
|
||||
final String? lens;
|
||||
final double? f;
|
||||
final double? mm;
|
||||
final int? iso;
|
||||
final double? exposureSeconds;
|
||||
@freezed
|
||||
abstract class ExifInfo with _$ExifInfo {
|
||||
const ExifInfo._();
|
||||
|
||||
const factory ExifInfo({
|
||||
int? assetId,
|
||||
int? fileSize,
|
||||
String? description,
|
||||
@Default(false) bool isFlipped,
|
||||
String? orientation,
|
||||
String? timeZone,
|
||||
DateTime? dateTimeOriginal,
|
||||
int? rating,
|
||||
int? width,
|
||||
int? height,
|
||||
|
||||
// GPS
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
String? city,
|
||||
String? state,
|
||||
String? country,
|
||||
|
||||
// Camera related
|
||||
String? make,
|
||||
String? model,
|
||||
String? lens,
|
||||
double? f,
|
||||
double? mm,
|
||||
int? iso,
|
||||
double? exposureSeconds,
|
||||
}) = _ExifInfo;
|
||||
|
||||
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
|
||||
|
||||
|
|
@ -41,162 +51,4 @@ class ExifInfo {
|
|||
String get fNumber => f == null ? "" : f!.toStringAsFixed(1);
|
||||
|
||||
String get focalLength => mm == null ? "" : mm!.toStringAsFixed(3);
|
||||
|
||||
const ExifInfo({
|
||||
this.assetId,
|
||||
this.fileSize,
|
||||
this.description,
|
||||
this.orientation,
|
||||
this.timeZone,
|
||||
this.dateTimeOriginal,
|
||||
this.rating,
|
||||
this.width,
|
||||
this.height,
|
||||
this.isFlipped = false,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.city,
|
||||
this.state,
|
||||
this.country,
|
||||
this.make,
|
||||
this.model,
|
||||
this.lens,
|
||||
this.f,
|
||||
this.mm,
|
||||
this.iso,
|
||||
this.exposureSeconds,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(covariant ExifInfo other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.fileSize == fileSize &&
|
||||
other.description == description &&
|
||||
other.isFlipped == isFlipped &&
|
||||
other.orientation == orientation &&
|
||||
other.timeZone == timeZone &&
|
||||
other.dateTimeOriginal == dateTimeOriginal &&
|
||||
other.rating == rating &&
|
||||
other.width == width &&
|
||||
other.height == height &&
|
||||
other.latitude == latitude &&
|
||||
other.longitude == longitude &&
|
||||
other.city == city &&
|
||||
other.state == state &&
|
||||
other.country == country &&
|
||||
other.make == make &&
|
||||
other.model == model &&
|
||||
other.lens == lens &&
|
||||
other.f == f &&
|
||||
other.mm == mm &&
|
||||
other.iso == iso &&
|
||||
other.exposureSeconds == exposureSeconds &&
|
||||
other.assetId == assetId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return fileSize.hashCode ^
|
||||
description.hashCode ^
|
||||
orientation.hashCode ^
|
||||
isFlipped.hashCode ^
|
||||
timeZone.hashCode ^
|
||||
dateTimeOriginal.hashCode ^
|
||||
rating.hashCode ^
|
||||
width.hashCode ^
|
||||
height.hashCode ^
|
||||
latitude.hashCode ^
|
||||
longitude.hashCode ^
|
||||
city.hashCode ^
|
||||
state.hashCode ^
|
||||
country.hashCode ^
|
||||
make.hashCode ^
|
||||
model.hashCode ^
|
||||
lens.hashCode ^
|
||||
f.hashCode ^
|
||||
mm.hashCode ^
|
||||
iso.hashCode ^
|
||||
exposureSeconds.hashCode ^
|
||||
assetId.hashCode;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''{
|
||||
fileSize: ${fileSize ?? 'NA'},
|
||||
description: ${description ?? 'NA'},
|
||||
orientation: ${orientation ?? 'NA'},
|
||||
isFlipped: $isFlipped,
|
||||
timeZone: ${timeZone ?? 'NA'},
|
||||
dateTimeOriginal: ${dateTimeOriginal ?? 'NA'},
|
||||
rating: ${rating ?? 'NA'},
|
||||
width: ${width ?? 'NA'},
|
||||
height: ${height ?? 'NA'},
|
||||
latitude: ${latitude ?? 'NA'},
|
||||
longitude: ${longitude ?? 'NA'},
|
||||
city: ${city ?? 'NA'},
|
||||
state: ${state ?? 'NA'},
|
||||
country: ${country ?? '<NA>'},
|
||||
make: ${make ?? 'NA'},
|
||||
model: ${model ?? 'NA'},
|
||||
lens: ${lens ?? 'NA'},
|
||||
f: ${f ?? 'NA'},
|
||||
mm: ${mm ?? '<NA>'},
|
||||
iso: ${iso ?? 'NA'},
|
||||
exposureSeconds: ${exposureSeconds ?? 'NA'},
|
||||
}''';
|
||||
}
|
||||
|
||||
ExifInfo copyWith({
|
||||
int? assetId,
|
||||
int? fileSize,
|
||||
String? description,
|
||||
String? orientation,
|
||||
String? timeZone,
|
||||
DateTime? dateTimeOriginal,
|
||||
int? rating,
|
||||
int? width,
|
||||
int? height,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
String? city,
|
||||
String? state,
|
||||
String? country,
|
||||
bool? isFlipped,
|
||||
String? make,
|
||||
String? model,
|
||||
String? lens,
|
||||
double? f,
|
||||
double? mm,
|
||||
int? iso,
|
||||
double? exposureSeconds,
|
||||
}) {
|
||||
return ExifInfo(
|
||||
assetId: assetId ?? this.assetId,
|
||||
fileSize: fileSize ?? this.fileSize,
|
||||
description: description ?? this.description,
|
||||
orientation: orientation ?? this.orientation,
|
||||
timeZone: timeZone ?? this.timeZone,
|
||||
dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal,
|
||||
rating: rating ?? this.rating,
|
||||
width: width ?? this.width,
|
||||
height: height ?? this.height,
|
||||
isFlipped: isFlipped ?? this.isFlipped,
|
||||
latitude: latitude ?? this.latitude,
|
||||
longitude: longitude ?? this.longitude,
|
||||
city: city ?? this.city,
|
||||
state: state ?? this.state,
|
||||
country: country ?? this.country,
|
||||
make: make ?? this.make,
|
||||
model: model ?? this.model,
|
||||
lens: lens ?? this.lens,
|
||||
f: f ?? this.f,
|
||||
mm: mm ?? this.mm,
|
||||
iso: iso ?? this.iso,
|
||||
exposureSeconds: exposureSeconds ?? this.exposureSeconds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,128 +1,24 @@
|
|||
class Ocr {
|
||||
final String id;
|
||||
final String assetId;
|
||||
final double x1;
|
||||
final double y1;
|
||||
final double x2;
|
||||
final double y2;
|
||||
final double x3;
|
||||
final double y3;
|
||||
final double x4;
|
||||
final double y4;
|
||||
final double boxScore;
|
||||
final double textScore;
|
||||
final String text;
|
||||
final bool isVisible;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
const Ocr({
|
||||
required this.id,
|
||||
required this.assetId,
|
||||
required this.x1,
|
||||
required this.y1,
|
||||
required this.x2,
|
||||
required this.y2,
|
||||
required this.x3,
|
||||
required this.y3,
|
||||
required this.x4,
|
||||
required this.y4,
|
||||
required this.boxScore,
|
||||
required this.textScore,
|
||||
required this.text,
|
||||
required this.isVisible,
|
||||
});
|
||||
part 'ocr.model.freezed.dart';
|
||||
|
||||
Ocr copyWith({
|
||||
String? id,
|
||||
String? assetId,
|
||||
double? x1,
|
||||
double? y1,
|
||||
double? x2,
|
||||
double? y2,
|
||||
double? x3,
|
||||
double? y3,
|
||||
double? x4,
|
||||
double? y4,
|
||||
double? boxScore,
|
||||
double? textScore,
|
||||
String? text,
|
||||
bool? isVisible,
|
||||
}) {
|
||||
return Ocr(
|
||||
id: id ?? this.id,
|
||||
assetId: assetId ?? this.assetId,
|
||||
x1: x1 ?? this.x1,
|
||||
y1: y1 ?? this.y1,
|
||||
x2: x2 ?? this.x2,
|
||||
y2: y2 ?? this.y2,
|
||||
x3: x3 ?? this.x3,
|
||||
y3: y3 ?? this.y3,
|
||||
x4: x4 ?? this.x4,
|
||||
y4: y4 ?? this.y4,
|
||||
boxScore: boxScore ?? this.boxScore,
|
||||
textScore: textScore ?? this.textScore,
|
||||
text: text ?? this.text,
|
||||
isVisible: isVisible ?? this.isVisible,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''Ocr {
|
||||
id: $id,
|
||||
assetId: $assetId,
|
||||
x1: $x1,
|
||||
y1: $y1,
|
||||
x2: $x2,
|
||||
y2: $y2,
|
||||
x3: $x3,
|
||||
y3: $y3,
|
||||
x4: $x4,
|
||||
y4: $y4,
|
||||
boxScore: $boxScore,
|
||||
textScore: $textScore,
|
||||
text: $text,
|
||||
isVisible: $isVisible
|
||||
}''';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other is Ocr &&
|
||||
other.id == id &&
|
||||
other.assetId == assetId &&
|
||||
other.x1 == x1 &&
|
||||
other.y1 == y1 &&
|
||||
other.x2 == x2 &&
|
||||
other.y2 == y2 &&
|
||||
other.x3 == x3 &&
|
||||
other.y3 == y3 &&
|
||||
other.x4 == x4 &&
|
||||
other.y4 == y4 &&
|
||||
other.boxScore == boxScore &&
|
||||
other.textScore == textScore &&
|
||||
other.text == text &&
|
||||
other.isVisible == isVisible;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^
|
||||
assetId.hashCode ^
|
||||
x1.hashCode ^
|
||||
y1.hashCode ^
|
||||
x2.hashCode ^
|
||||
y2.hashCode ^
|
||||
x3.hashCode ^
|
||||
y3.hashCode ^
|
||||
x4.hashCode ^
|
||||
y4.hashCode ^
|
||||
boxScore.hashCode ^
|
||||
textScore.hashCode ^
|
||||
text.hashCode ^
|
||||
isVisible.hashCode;
|
||||
}
|
||||
@freezed
|
||||
abstract class Ocr with _$Ocr {
|
||||
const factory Ocr({
|
||||
required String id,
|
||||
required String assetId,
|
||||
required double x1,
|
||||
required double y1,
|
||||
required double x2,
|
||||
required double y2,
|
||||
required double x3,
|
||||
required double y3,
|
||||
required double x4,
|
||||
required double y4,
|
||||
required double boxScore,
|
||||
required double textScore,
|
||||
required String text,
|
||||
required bool isVisible,
|
||||
}) = _Ocr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'person.model.freezed.dart';
|
||||
|
||||
// TODO: Remove PersonDto once Isar is removed
|
||||
class PersonDto {
|
||||
const PersonDto({
|
||||
|
|
@ -93,102 +98,18 @@ class PersonDto {
|
|||
}
|
||||
|
||||
// Model for a person stored in the server
|
||||
class DriftPerson {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String ownerId;
|
||||
final String name;
|
||||
final String? faceAssetId;
|
||||
final bool isFavorite;
|
||||
final bool isHidden;
|
||||
final String? color;
|
||||
final DateTime? birthDate;
|
||||
|
||||
const DriftPerson({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.ownerId,
|
||||
required this.name,
|
||||
this.faceAssetId,
|
||||
required this.isFavorite,
|
||||
required this.isHidden,
|
||||
required this.color,
|
||||
this.birthDate,
|
||||
});
|
||||
|
||||
DriftPerson copyWith({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
String? ownerId,
|
||||
String? name,
|
||||
@freezed
|
||||
abstract class DriftPerson with _$DriftPerson {
|
||||
const factory DriftPerson({
|
||||
required String id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required String ownerId,
|
||||
required String name,
|
||||
String? faceAssetId,
|
||||
bool? isFavorite,
|
||||
bool? isHidden,
|
||||
String? color,
|
||||
required bool isFavorite,
|
||||
required bool isHidden,
|
||||
required String? color,
|
||||
DateTime? birthDate,
|
||||
}) {
|
||||
return DriftPerson(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
name: name ?? this.name,
|
||||
faceAssetId: faceAssetId ?? this.faceAssetId,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
isHidden: isHidden ?? this.isHidden,
|
||||
color: color ?? this.color,
|
||||
birthDate: birthDate ?? this.birthDate,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''Person {
|
||||
id: $id,
|
||||
createdAt: $createdAt,
|
||||
updatedAt: $updatedAt,
|
||||
ownerId: $ownerId,
|
||||
name: $name,
|
||||
faceAssetId: ${faceAssetId ?? "<NA>"},
|
||||
isFavorite: $isFavorite,
|
||||
isHidden: $isHidden,
|
||||
color: ${color ?? "<NA>"},
|
||||
birthDate: ${birthDate ?? "<NA>"}
|
||||
}''';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant DriftPerson other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.id == id &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.ownerId == ownerId &&
|
||||
other.name == name &&
|
||||
other.faceAssetId == faceAssetId &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isHidden == isHidden &&
|
||||
other.color == color &&
|
||||
other.birthDate == birthDate;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^
|
||||
createdAt.hashCode ^
|
||||
updatedAt.hashCode ^
|
||||
ownerId.hashCode ^
|
||||
name.hashCode ^
|
||||
faceAssetId.hashCode ^
|
||||
isFavorite.hashCode ^
|
||||
isHidden.hashCode ^
|
||||
color.hashCode ^
|
||||
birthDate.hashCode;
|
||||
}
|
||||
}) = _DriftPerson;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +1,18 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'stack.model.freezed.dart';
|
||||
|
||||
// Model for a stack stored in the server
|
||||
class Stack {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String ownerId;
|
||||
final String primaryAssetId;
|
||||
|
||||
const Stack({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.ownerId,
|
||||
required this.primaryAssetId,
|
||||
});
|
||||
|
||||
Stack copyWith({String? id, DateTime? createdAt, DateTime? updatedAt, String? ownerId, String? primaryAssetId}) {
|
||||
return Stack(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
primaryAssetId: primaryAssetId ?? this.primaryAssetId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''Stack {
|
||||
id: $id,
|
||||
createdAt: $createdAt,
|
||||
updatedAt: $updatedAt,
|
||||
ownerId: $ownerId,
|
||||
primaryAssetId: $primaryAssetId
|
||||
}''';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant Stack other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.id == id &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.ownerId == ownerId &&
|
||||
other.primaryAssetId == primaryAssetId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^ createdAt.hashCode ^ updatedAt.hashCode ^ ownerId.hashCode ^ primaryAssetId.hashCode;
|
||||
}
|
||||
@freezed
|
||||
abstract class Stack with _$Stack {
|
||||
const factory Stack({
|
||||
required String id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required String ownerId,
|
||||
required String primaryAssetId,
|
||||
}) = _Stack;
|
||||
}
|
||||
|
||||
class StackResponse {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
|
||||
part 'user_metadata.model.freezed.dart';
|
||||
|
||||
enum UserMetadataKey {
|
||||
// do not change this order!
|
||||
onboarding,
|
||||
|
|
@ -224,61 +228,17 @@ licenseKey: $licenseKey,
|
|||
}
|
||||
|
||||
// Model for a user metadata stored in the server
|
||||
class UserMetadata {
|
||||
final String userId;
|
||||
final UserMetadataKey key;
|
||||
final Onboarding? onboarding;
|
||||
final Preferences? preferences;
|
||||
final License? license;
|
||||
|
||||
const UserMetadata({required this.userId, required this.key, this.onboarding, this.preferences, this.license})
|
||||
: assert(
|
||||
onboarding != null || preferences != null || license != null,
|
||||
'One of onboarding, preferences and license must be provided',
|
||||
);
|
||||
|
||||
UserMetadata copyWith({
|
||||
String? userId,
|
||||
UserMetadataKey? key,
|
||||
@freezed
|
||||
abstract class UserMetadata with _$UserMetadata {
|
||||
@Assert(
|
||||
'onboarding != null || preferences != null || license != null',
|
||||
'One of onboarding, preferences and license must be provided',
|
||||
)
|
||||
const factory UserMetadata({
|
||||
required String userId,
|
||||
required UserMetadataKey key,
|
||||
Onboarding? onboarding,
|
||||
Preferences? preferences,
|
||||
License? license,
|
||||
}) {
|
||||
return UserMetadata(
|
||||
userId: userId ?? this.userId,
|
||||
key: key ?? this.key,
|
||||
onboarding: onboarding ?? this.onboarding,
|
||||
preferences: preferences ?? this.preferences,
|
||||
license: license ?? this.license,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''UserMetadata: {
|
||||
userId: $userId,
|
||||
key: $key,
|
||||
onboarding: ${onboarding ?? "<NA>"},
|
||||
preferences: ${preferences ?? "<NA>"},
|
||||
license: ${license ?? "<NA>"},
|
||||
}''';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant UserMetadata other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.userId == userId &&
|
||||
other.key == key &&
|
||||
other.onboarding == onboarding &&
|
||||
other.preferences == preferences &&
|
||||
other.license == license;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return userId.hashCode ^ key.hashCode ^ onboarding.hashCode ^ preferences.hashCode ^ license.hashCode;
|
||||
}
|
||||
}) = _UserMetadata;
|
||||
}
|
||||
|
|
|
|||
47
mobile/lib/domain/services/device_permission.service.dart
Normal file
47
mobile/lib/domain/services/device_permission.service.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
||||
|
||||
typedef _Handler = Future<DevicePermissionStatus> Function(DevicePermission);
|
||||
|
||||
class DevicePermissionService {
|
||||
const DevicePermissionService(this._permissionRepository);
|
||||
|
||||
final DevicePermissionRepository _permissionRepository;
|
||||
|
||||
Future<DevicePermissionStatus> galleryStatus() => _gallery(_permissionRepository.getStatus);
|
||||
|
||||
Future<DevicePermissionStatus> requestGallery() => _gallery(_permissionRepository.request);
|
||||
|
||||
Future<DevicePermissionStatus> _gallery(_Handler handler) async {
|
||||
if (CurrentPlatform.isIOS) {
|
||||
return handler(.photos);
|
||||
}
|
||||
|
||||
final sdkVersion = await _permissionRepository.getAndroidSdkVersion();
|
||||
const maxExternalStorageSdk = 32; // READ/WRITE_EXTERNAL_STORAGE - Android 12.1
|
||||
final status = sdkVersion <= maxExternalStorageSdk ? await handler(.storage) : await _photosAndVideos(handler);
|
||||
|
||||
const minMediaLocationSdk = 29; // ACCESS_MEDIA_LOCATION - Android 10
|
||||
if (status.hasAccess && sdkVersion >= minMediaLocationSdk) {
|
||||
final mediaLocation = await handler(.mediaLocation);
|
||||
return mediaLocation.hasAccess ? status : mediaLocation;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Future<DevicePermissionStatus> _photosAndVideos(_Handler handler) async {
|
||||
final photos = await handler(.photos);
|
||||
if (!photos.hasAccess) {
|
||||
return photos;
|
||||
}
|
||||
|
||||
final videos = await handler(.videos);
|
||||
if (!videos.hasAccess) {
|
||||
return videos;
|
||||
}
|
||||
|
||||
return photos == .granted && videos == .granted ? .granted : .limited;
|
||||
}
|
||||
}
|
||||
|
|
@ -66,11 +66,12 @@ class HashService {
|
|||
await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true);
|
||||
}
|
||||
}
|
||||
} on PlatformException catch (e) {
|
||||
} on PlatformException catch (e, s) {
|
||||
if (e.code == _kHashCancelledCode) {
|
||||
_log.warning("Hashing cancelled by platform");
|
||||
return;
|
||||
}
|
||||
_log.severe("Native hashing failed: ${e.code}", e, s);
|
||||
} catch (e, s) {
|
||||
_log.severe("Error during hashing", e, s);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class LocalSyncService {
|
|||
final NativeSyncApi _nativeSyncApi;
|
||||
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
||||
final AssetMediaRepository _assetMediaRepository;
|
||||
final IPermissionRepository _permissionRepository;
|
||||
final DevicePermissionRepository _permissionRepository;
|
||||
final Completer<void>? _cancellation;
|
||||
final Logger _log = Logger("DeviceSyncService");
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class SyncStreamService {
|
|||
final DriftLocalAssetRepository _localAssetRepository;
|
||||
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
||||
final AssetMediaRepository _assetMediaRepository;
|
||||
final IPermissionRepository _permissionRepository;
|
||||
final DevicePermissionRepository _permissionRepository;
|
||||
final SyncMigrationRepository _syncMigrationRepository;
|
||||
final ApiService _api;
|
||||
final Completer<void>? _cancellation;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class BackgroundSyncManager {
|
|||
final SyncErrorCallback? onCloudIdSyncError;
|
||||
|
||||
Cancelable<bool?>? _syncTask;
|
||||
bool _syncQueued = false;
|
||||
Cancelable<void>? _syncWebsocketTask;
|
||||
Cancelable<void>? _cloudIdSyncTask;
|
||||
Cancelable<void>? _deviceAlbumSyncTask;
|
||||
|
|
@ -50,6 +51,7 @@ class BackgroundSyncManager {
|
|||
});
|
||||
|
||||
Future<void> cancel() async {
|
||||
_syncQueued = false;
|
||||
final tasks = [
|
||||
_syncTask,
|
||||
_syncWebsocketTask,
|
||||
|
|
@ -133,8 +135,9 @@ class BackgroundSyncManager {
|
|||
});
|
||||
}
|
||||
|
||||
Future<bool> syncRemote() {
|
||||
Future<bool> syncRemote({bool enqueue = false}) {
|
||||
if (_syncTask != null) {
|
||||
_syncQueued |= enqueue;
|
||||
return _syncTask!.future.then((result) => result ?? false).catchError((_) => false);
|
||||
}
|
||||
|
||||
|
|
@ -148,15 +151,20 @@ class BackgroundSyncManager {
|
|||
.then((result) {
|
||||
final success = result ?? false;
|
||||
onRemoteSyncComplete?.call(success);
|
||||
_syncQueued &= success;
|
||||
return success;
|
||||
})
|
||||
.catchError((error) {
|
||||
onRemoteSyncError?.call(error.toString());
|
||||
_syncTask = null;
|
||||
_syncQueued = false;
|
||||
return false;
|
||||
})
|
||||
.whenComplete(() {
|
||||
_syncTask = null;
|
||||
if (_syncQueued) {
|
||||
_syncQueued = false;
|
||||
unawaited(syncRemote());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,65 +1,21 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
|
||||
part 'activity.model.freezed.dart';
|
||||
|
||||
enum ActivityType { comment, like }
|
||||
|
||||
class Activity {
|
||||
final String id;
|
||||
final String? assetId;
|
||||
final String? comment;
|
||||
final DateTime createdAt;
|
||||
final ActivityType type;
|
||||
final UserDto user;
|
||||
|
||||
const Activity({
|
||||
required this.id,
|
||||
this.assetId,
|
||||
this.comment,
|
||||
required this.createdAt,
|
||||
required this.type,
|
||||
required this.user,
|
||||
});
|
||||
|
||||
Activity copyWith({
|
||||
String? id,
|
||||
@freezed
|
||||
abstract class Activity with _$Activity {
|
||||
const factory Activity({
|
||||
required String id,
|
||||
String? assetId,
|
||||
String? comment,
|
||||
DateTime? createdAt,
|
||||
ActivityType? type,
|
||||
UserDto? user,
|
||||
}) {
|
||||
return Activity(
|
||||
id: id ?? this.id,
|
||||
assetId: assetId ?? this.assetId,
|
||||
comment: comment ?? this.comment,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
type: type ?? this.type,
|
||||
user: user ?? this.user,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Activity(id: $id, assetId: $assetId, comment: $comment, createdAt: $createdAt, type: $type, user: $user)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant Activity other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.id == id &&
|
||||
other.assetId == assetId &&
|
||||
other.comment == comment &&
|
||||
other.createdAt == createdAt &&
|
||||
other.type == type &&
|
||||
other.user == user;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^ assetId.hashCode ^ comment.hashCode ^ createdAt.hashCode ^ type.hashCode ^ user.hashCode;
|
||||
}
|
||||
required DateTime createdAt,
|
||||
required ActivityType type,
|
||||
required UserDto user,
|
||||
}) = _Activity;
|
||||
}
|
||||
|
||||
class ActivityStats {
|
||||
|
|
|
|||
|
|
@ -1,71 +1,17 @@
|
|||
class AuthState {
|
||||
final String deviceId;
|
||||
final String userId;
|
||||
final String userEmail;
|
||||
final bool isAuthenticated;
|
||||
final String name;
|
||||
final bool isAdmin;
|
||||
final String profileImagePath;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
const AuthState({
|
||||
required this.deviceId,
|
||||
required this.userId,
|
||||
required this.userEmail,
|
||||
required this.isAuthenticated,
|
||||
required this.name,
|
||||
required this.isAdmin,
|
||||
required this.profileImagePath,
|
||||
});
|
||||
part 'auth_state.model.freezed.dart';
|
||||
|
||||
AuthState copyWith({
|
||||
String? deviceId,
|
||||
String? userId,
|
||||
String? userEmail,
|
||||
bool? isAuthenticated,
|
||||
String? name,
|
||||
bool? isAdmin,
|
||||
String? profileImagePath,
|
||||
}) {
|
||||
return AuthState(
|
||||
deviceId: deviceId ?? this.deviceId,
|
||||
userId: userId ?? this.userId,
|
||||
userEmail: userEmail ?? this.userEmail,
|
||||
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
|
||||
name: name ?? this.name,
|
||||
isAdmin: isAdmin ?? this.isAdmin,
|
||||
profileImagePath: profileImagePath ?? this.profileImagePath,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthenticationState(deviceId: $deviceId, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, name: $name, isAdmin: $isAdmin, profileImagePath: $profileImagePath)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other is AuthState &&
|
||||
other.deviceId == deviceId &&
|
||||
other.userId == userId &&
|
||||
other.userEmail == userEmail &&
|
||||
other.isAuthenticated == isAuthenticated &&
|
||||
other.name == name &&
|
||||
other.isAdmin == isAdmin &&
|
||||
other.profileImagePath == profileImagePath;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return deviceId.hashCode ^
|
||||
userId.hashCode ^
|
||||
userEmail.hashCode ^
|
||||
isAuthenticated.hashCode ^
|
||||
name.hashCode ^
|
||||
isAdmin.hashCode ^
|
||||
profileImagePath.hashCode;
|
||||
}
|
||||
@freezed
|
||||
abstract class AuthState with _$AuthState {
|
||||
const factory AuthState({
|
||||
required String deviceId,
|
||||
required String userId,
|
||||
required String userEmail,
|
||||
required bool isAuthenticated,
|
||||
required String name,
|
||||
required bool isAdmin,
|
||||
required String profileImagePath,
|
||||
}) = _AuthState;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,14 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class MapMarker {
|
||||
final LatLng latLng;
|
||||
final String assetRemoteId;
|
||||
const MapMarker({required this.latLng, required this.assetRemoteId});
|
||||
part 'map_marker.model.freezed.dart';
|
||||
|
||||
MapMarker copyWith({LatLng? latLng, String? assetRemoteId}) {
|
||||
return MapMarker(latLng: latLng ?? this.latLng, assetRemoteId: assetRemoteId ?? this.assetRemoteId);
|
||||
}
|
||||
@freezed
|
||||
abstract class MapMarker with _$MapMarker {
|
||||
const factory MapMarker({required LatLng latLng, required String assetRemoteId}) = _MapMarker;
|
||||
|
||||
MapMarker.fromDto(MapMarkerResponseDto dto) : latLng = LatLng(dto.lat, dto.lon), assetRemoteId = dto.id;
|
||||
|
||||
@override
|
||||
String toString() => 'MapMarker(latLng: $latLng, assetRemoteId: $assetRemoteId)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant MapMarker other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.latLng == latLng && other.assetRemoteId == assetRemoteId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => latLng.hashCode ^ assetRemoteId.hashCode;
|
||||
factory MapMarker.fromDto(MapMarkerResponseDto dto) =>
|
||||
MapMarker(latLng: LatLng(dto.lat, dto.lon), assetRemoteId: dto.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +1,20 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class MapState {
|
||||
final ThemeMode themeMode;
|
||||
final bool showFavoriteOnly;
|
||||
final bool includeArchived;
|
||||
final bool withPartners;
|
||||
final int relativeTime;
|
||||
final bool shouldRefetchMarkers;
|
||||
final AsyncValue<String> lightStyleFetched;
|
||||
final AsyncValue<String> darkStyleFetched;
|
||||
part 'map_state.model.freezed.dart';
|
||||
|
||||
const MapState({
|
||||
this.themeMode = ThemeMode.system,
|
||||
this.showFavoriteOnly = false,
|
||||
this.includeArchived = false,
|
||||
this.withPartners = false,
|
||||
this.relativeTime = 0,
|
||||
this.shouldRefetchMarkers = false,
|
||||
this.lightStyleFetched = const AsyncLoading(),
|
||||
this.darkStyleFetched = const AsyncLoading(),
|
||||
});
|
||||
|
||||
MapState copyWith({
|
||||
ThemeMode? themeMode,
|
||||
bool? showFavoriteOnly,
|
||||
bool? includeArchived,
|
||||
bool? withPartners,
|
||||
int? relativeTime,
|
||||
bool? shouldRefetchMarkers,
|
||||
AsyncValue<String>? lightStyleFetched,
|
||||
AsyncValue<String>? darkStyleFetched,
|
||||
}) {
|
||||
return MapState(
|
||||
themeMode: themeMode ?? this.themeMode,
|
||||
showFavoriteOnly: showFavoriteOnly ?? this.showFavoriteOnly,
|
||||
includeArchived: includeArchived ?? this.includeArchived,
|
||||
withPartners: withPartners ?? this.withPartners,
|
||||
relativeTime: relativeTime ?? this.relativeTime,
|
||||
shouldRefetchMarkers: shouldRefetchMarkers ?? this.shouldRefetchMarkers,
|
||||
lightStyleFetched: lightStyleFetched ?? this.lightStyleFetched,
|
||||
darkStyleFetched: darkStyleFetched ?? this.darkStyleFetched,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MapState(themeMode: $themeMode, showFavoriteOnly: $showFavoriteOnly, includeArchived: $includeArchived, withPartners: $withPartners, relativeTime: $relativeTime, shouldRefetchMarkers: $shouldRefetchMarkers, lightStyleFetched: $lightStyleFetched, darkStyleFetched: $darkStyleFetched)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant MapState other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.themeMode == themeMode &&
|
||||
other.showFavoriteOnly == showFavoriteOnly &&
|
||||
other.includeArchived == includeArchived &&
|
||||
other.withPartners == withPartners &&
|
||||
other.relativeTime == relativeTime &&
|
||||
other.shouldRefetchMarkers == shouldRefetchMarkers &&
|
||||
other.lightStyleFetched == lightStyleFetched &&
|
||||
other.darkStyleFetched == darkStyleFetched;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return themeMode.hashCode ^
|
||||
showFavoriteOnly.hashCode ^
|
||||
includeArchived.hashCode ^
|
||||
withPartners.hashCode ^
|
||||
relativeTime.hashCode ^
|
||||
shouldRefetchMarkers.hashCode ^
|
||||
lightStyleFetched.hashCode ^
|
||||
darkStyleFetched.hashCode;
|
||||
}
|
||||
@freezed
|
||||
abstract class MapState with _$MapState {
|
||||
const factory MapState({
|
||||
@Default(ThemeMode.system) ThemeMode themeMode,
|
||||
@Default(false) bool showFavoriteOnly,
|
||||
@Default(false) bool includeArchived,
|
||||
@Default(false) bool withPartners,
|
||||
@Default(0) int relativeTime,
|
||||
@Default(false) bool shouldRefetchMarkers,
|
||||
@Default(AsyncLoading<String>()) AsyncValue<String> lightStyleFetched,
|
||||
@Default(AsyncLoading<String>()) AsyncValue<String> darkStyleFetched,
|
||||
}) = _MapState;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,22 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class ServerDiskInfo {
|
||||
final String diskAvailable;
|
||||
final String diskSize;
|
||||
final String diskUse;
|
||||
final double diskUsagePercentage;
|
||||
part 'server_disk_info.model.freezed.dart';
|
||||
|
||||
const ServerDiskInfo({
|
||||
required this.diskAvailable,
|
||||
required this.diskSize,
|
||||
required this.diskUse,
|
||||
required this.diskUsagePercentage,
|
||||
});
|
||||
@freezed
|
||||
abstract class ServerDiskInfo with _$ServerDiskInfo {
|
||||
const factory ServerDiskInfo({
|
||||
required String diskAvailable,
|
||||
required String diskSize,
|
||||
required String diskUse,
|
||||
required double diskUsagePercentage,
|
||||
}) = _ServerDiskInfo;
|
||||
|
||||
ServerDiskInfo copyWith({String? diskAvailable, String? diskSize, String? diskUse, double? diskUsagePercentage}) {
|
||||
return ServerDiskInfo(
|
||||
diskAvailable: diskAvailable ?? this.diskAvailable,
|
||||
diskSize: diskSize ?? this.diskSize,
|
||||
diskUse: diskUse ?? this.diskUse,
|
||||
diskUsagePercentage: diskUsagePercentage ?? this.diskUsagePercentage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServerDiskInfo(diskAvailable: $diskAvailable, diskSize: $diskSize, diskUse: $diskUse, diskUsagePercentage: $diskUsagePercentage)';
|
||||
}
|
||||
|
||||
ServerDiskInfo.fromDto(ServerStorageResponseDto dto)
|
||||
: diskAvailable = dto.diskAvailable,
|
||||
diskSize = dto.diskSize,
|
||||
diskUse = dto.diskUse,
|
||||
diskUsagePercentage = dto.diskUsagePercentage;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other is ServerDiskInfo &&
|
||||
other.diskAvailable == diskAvailable &&
|
||||
other.diskSize == diskSize &&
|
||||
other.diskUse == diskUse &&
|
||||
other.diskUsagePercentage == diskUsagePercentage;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return diskAvailable.hashCode ^ diskSize.hashCode ^ diskUse.hashCode ^ diskUsagePercentage.hashCode;
|
||||
}
|
||||
factory ServerDiskInfo.fromDto(ServerStorageResponseDto dto) => ServerDiskInfo(
|
||||
diskAvailable: dto.diskAvailable,
|
||||
diskSize: dto.diskSize,
|
||||
diskUse: dto.diskUse,
|
||||
diskUsagePercentage: dto.diskUsagePercentage,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +1,26 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class ServerFeatures {
|
||||
final bool trash;
|
||||
final bool map;
|
||||
final bool oauthEnabled;
|
||||
final bool passwordLogin;
|
||||
final bool ocr;
|
||||
final bool smartSearch;
|
||||
part 'server_features.model.freezed.dart';
|
||||
|
||||
const ServerFeatures({
|
||||
required this.trash,
|
||||
required this.map,
|
||||
required this.oauthEnabled,
|
||||
required this.passwordLogin,
|
||||
this.ocr = false,
|
||||
this.smartSearch = false,
|
||||
});
|
||||
@freezed
|
||||
abstract class ServerFeatures with _$ServerFeatures {
|
||||
const factory ServerFeatures({
|
||||
required bool trash,
|
||||
required bool map,
|
||||
required bool oauthEnabled,
|
||||
required bool passwordLogin,
|
||||
@Default(false) bool ocr,
|
||||
@Default(false) bool smartSearch,
|
||||
}) = _ServerFeatures;
|
||||
|
||||
ServerFeatures copyWith({
|
||||
bool? trash,
|
||||
bool? map,
|
||||
bool? oauthEnabled,
|
||||
bool? passwordLogin,
|
||||
bool? ocr,
|
||||
bool? smartSearch,
|
||||
}) {
|
||||
return ServerFeatures(
|
||||
trash: trash ?? this.trash,
|
||||
map: map ?? this.map,
|
||||
oauthEnabled: oauthEnabled ?? this.oauthEnabled,
|
||||
passwordLogin: passwordLogin ?? this.passwordLogin,
|
||||
ocr: ocr ?? this.ocr,
|
||||
smartSearch: smartSearch ?? this.smartSearch,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServerFeatures(trash: $trash, map: $map, oauthEnabled: $oauthEnabled, passwordLogin: $passwordLogin, ocr: $ocr, smartSearch: $smartSearch)';
|
||||
}
|
||||
|
||||
ServerFeatures.fromDto(ServerFeaturesDto dto)
|
||||
: trash = dto.trash,
|
||||
map = dto.map,
|
||||
oauthEnabled = dto.oauth,
|
||||
passwordLogin = dto.passwordLogin,
|
||||
ocr = dto.ocr,
|
||||
smartSearch = dto.smartSearch;
|
||||
|
||||
@override
|
||||
bool operator ==(covariant ServerFeatures other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.trash == trash &&
|
||||
other.map == map &&
|
||||
other.oauthEnabled == oauthEnabled &&
|
||||
other.passwordLogin == passwordLogin &&
|
||||
other.ocr == ocr &&
|
||||
other.smartSearch == smartSearch;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return trash.hashCode ^
|
||||
map.hashCode ^
|
||||
oauthEnabled.hashCode ^
|
||||
passwordLogin.hashCode ^
|
||||
ocr.hashCode ^
|
||||
smartSearch.hashCode;
|
||||
}
|
||||
factory ServerFeatures.fromDto(ServerFeaturesDto dto) => ServerFeatures(
|
||||
trash: dto.trash,
|
||||
map: dto.map,
|
||||
oauthEnabled: dto.oauth,
|
||||
passwordLogin: dto.passwordLogin,
|
||||
ocr: dto.ocr,
|
||||
smartSearch: dto.smartSearch,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_config.model.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_features.model.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||
|
||||
part 'server_info.model.freezed.dart';
|
||||
|
||||
enum VersionStatus {
|
||||
upToDate,
|
||||
clientOutOfDate,
|
||||
|
|
@ -18,68 +22,14 @@ enum VersionStatus {
|
|||
};
|
||||
}
|
||||
|
||||
class ServerInfo {
|
||||
final ServerVersion serverVersion;
|
||||
final ServerVersion? latestVersion;
|
||||
final ServerFeatures serverFeatures;
|
||||
final ServerConfig serverConfig;
|
||||
final ServerDiskInfo serverDiskInfo;
|
||||
final VersionStatus versionStatus;
|
||||
|
||||
const ServerInfo({
|
||||
required this.serverVersion,
|
||||
this.latestVersion,
|
||||
required this.serverFeatures,
|
||||
required this.serverConfig,
|
||||
required this.serverDiskInfo,
|
||||
required this.versionStatus,
|
||||
});
|
||||
|
||||
ServerInfo copyWith({
|
||||
ServerVersion? serverVersion,
|
||||
@freezed
|
||||
abstract class ServerInfo with _$ServerInfo {
|
||||
const factory ServerInfo({
|
||||
required ServerVersion serverVersion,
|
||||
ServerVersion? latestVersion,
|
||||
ServerFeatures? serverFeatures,
|
||||
ServerConfig? serverConfig,
|
||||
ServerDiskInfo? serverDiskInfo,
|
||||
VersionStatus? versionStatus,
|
||||
}) {
|
||||
return ServerInfo(
|
||||
serverVersion: serverVersion ?? this.serverVersion,
|
||||
latestVersion: latestVersion ?? this.latestVersion,
|
||||
serverFeatures: serverFeatures ?? this.serverFeatures,
|
||||
serverConfig: serverConfig ?? this.serverConfig,
|
||||
serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo,
|
||||
versionStatus: versionStatus ?? this.versionStatus,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, versionStatus: $versionStatus)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other is ServerInfo &&
|
||||
other.serverVersion == serverVersion &&
|
||||
other.latestVersion == latestVersion &&
|
||||
other.serverFeatures == serverFeatures &&
|
||||
other.serverConfig == serverConfig &&
|
||||
other.serverDiskInfo == serverDiskInfo &&
|
||||
other.versionStatus == versionStatus;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return serverVersion.hashCode ^
|
||||
latestVersion.hashCode ^
|
||||
serverFeatures.hashCode ^
|
||||
serverConfig.hashCode ^
|
||||
serverDiskInfo.hashCode ^
|
||||
versionStatus.hashCode;
|
||||
}
|
||||
required ServerFeatures serverFeatures,
|
||||
required ServerConfig serverConfig,
|
||||
required ServerDiskInfo serverDiskInfo,
|
||||
required VersionStatus versionStatus,
|
||||
}) = _ServerInfo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,119 +1,47 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
part 'shared_link.model.freezed.dart';
|
||||
|
||||
enum SharedLinkSource { album, individual }
|
||||
|
||||
class SharedLink {
|
||||
final String id;
|
||||
final String title;
|
||||
final bool allowDownload;
|
||||
final bool allowUpload;
|
||||
final String? thumbAssetId;
|
||||
final String? description;
|
||||
final String? password;
|
||||
final DateTime? expiresAt;
|
||||
final String key;
|
||||
final bool showMetadata;
|
||||
final SharedLinkSource type;
|
||||
final String? slug;
|
||||
@freezed
|
||||
abstract class SharedLink with _$SharedLink {
|
||||
const factory SharedLink({
|
||||
required String id,
|
||||
required String title,
|
||||
required bool allowDownload,
|
||||
required bool allowUpload,
|
||||
required String? thumbAssetId,
|
||||
required String? description,
|
||||
required String? password,
|
||||
required DateTime? expiresAt,
|
||||
required String key,
|
||||
required bool showMetadata,
|
||||
required SharedLinkSource type,
|
||||
required String? slug,
|
||||
}) = _SharedLink;
|
||||
|
||||
const SharedLink({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.allowDownload,
|
||||
required this.allowUpload,
|
||||
required this.thumbAssetId,
|
||||
required this.description,
|
||||
required this.password,
|
||||
required this.expiresAt,
|
||||
required this.key,
|
||||
required this.showMetadata,
|
||||
required this.type,
|
||||
required this.slug,
|
||||
});
|
||||
|
||||
SharedLink copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? thumbAssetId,
|
||||
bool? allowDownload,
|
||||
bool? allowUpload,
|
||||
String? description,
|
||||
String? password,
|
||||
DateTime? expiresAt,
|
||||
String? key,
|
||||
bool? showMetadata,
|
||||
SharedLinkSource? type,
|
||||
String? slug,
|
||||
}) {
|
||||
factory SharedLink.fromDto(SharedLinkResponseDto dto) {
|
||||
final isAlbum = dto.type == SharedLinkType.ALBUM;
|
||||
return SharedLink(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
thumbAssetId: thumbAssetId ?? this.thumbAssetId,
|
||||
allowDownload: allowDownload ?? this.allowDownload,
|
||||
allowUpload: allowUpload ?? this.allowUpload,
|
||||
description: description ?? this.description,
|
||||
password: password ?? this.password,
|
||||
expiresAt: expiresAt ?? this.expiresAt,
|
||||
key: key ?? this.key,
|
||||
showMetadata: showMetadata ?? this.showMetadata,
|
||||
type: type ?? this.type,
|
||||
slug: slug ?? this.slug,
|
||||
);
|
||||
}
|
||||
|
||||
SharedLink.fromDto(SharedLinkResponseDto dto)
|
||||
: id = dto.id,
|
||||
allowDownload = dto.allowDownload,
|
||||
allowUpload = dto.allowUpload,
|
||||
description = dto.description,
|
||||
password = dto.password,
|
||||
expiresAt = dto.expiresAt,
|
||||
key = dto.key,
|
||||
showMetadata = dto.showMetadata,
|
||||
slug = dto.slug,
|
||||
type = dto.type == SharedLinkType.ALBUM ? SharedLinkSource.album : SharedLinkSource.individual,
|
||||
title = dto.type == SharedLinkType.ALBUM
|
||||
? dto.album.orElse(null)?.albumName.toUpperCase() ?? "UNKNOWN SHARE"
|
||||
: "INDIVIDUAL SHARE",
|
||||
thumbAssetId = dto.type == SharedLinkType.ALBUM
|
||||
id: dto.id,
|
||||
allowDownload: dto.allowDownload,
|
||||
allowUpload: dto.allowUpload,
|
||||
description: dto.description,
|
||||
password: dto.password,
|
||||
expiresAt: dto.expiresAt,
|
||||
key: dto.key,
|
||||
showMetadata: dto.showMetadata,
|
||||
slug: dto.slug,
|
||||
type: isAlbum ? SharedLinkSource.album : SharedLinkSource.individual,
|
||||
title: isAlbum ? dto.album.orElse(null)?.albumName.toUpperCase() ?? "UNKNOWN SHARE" : "INDIVIDUAL SHARE",
|
||||
thumbAssetId: isAlbum
|
||||
? dto.album.orElse(null)?.albumThumbnailAssetId
|
||||
: dto.assets.isNotEmpty
|
||||
? dto.assets[0].id
|
||||
: null;
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'SharedLink(id=$id, title=$title, thumbAssetId=$thumbAssetId, allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, password=$password, expiresAt=$expiresAt, key=$key, showMetadata=$showMetadata, type=$type, slug=$slug)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SharedLink &&
|
||||
other.id == id &&
|
||||
other.title == title &&
|
||||
other.thumbAssetId == thumbAssetId &&
|
||||
other.allowDownload == allowDownload &&
|
||||
other.allowUpload == allowUpload &&
|
||||
other.description == description &&
|
||||
other.password == password &&
|
||||
other.expiresAt == expiresAt &&
|
||||
other.key == key &&
|
||||
other.showMetadata == showMetadata &&
|
||||
other.type == type &&
|
||||
other.slug == slug;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
id.hashCode ^
|
||||
title.hashCode ^
|
||||
thumbAssetId.hashCode ^
|
||||
allowDownload.hashCode ^
|
||||
allowUpload.hashCode ^
|
||||
description.hashCode ^
|
||||
password.hashCode ^
|
||||
expiresAt.hashCode ^
|
||||
key.hashCode ^
|
||||
showMetadata.hashCode ^
|
||||
type.hashCode ^
|
||||
slug.hashCode;
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||
|
||||
final error = ref.watch(driftBackupProvider.select((p) => p.error));
|
||||
|
||||
final backupNotifier = ref.read(driftBackupProvider.notifier);
|
||||
final backupSyncManager = ref.read(backgroundSyncProvider);
|
||||
final backupNotifier = ref.watch(driftBackupProvider.notifier);
|
||||
final backupSyncManager = ref.watch(backgroundSyncProvider);
|
||||
|
||||
Future<void> startBackup() async {
|
||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||
|
|
@ -143,7 +143,7 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||
onStart: () async => await startBackup(),
|
||||
onStop: () {
|
||||
syncSuccess = null;
|
||||
backupNotifier.stopForegroundBackup();
|
||||
backupNotifier.stopForegroundBackup(reason: "backup button toggled off");
|
||||
},
|
||||
),
|
||||
switch (error) {
|
||||
|
|
@ -321,7 +321,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
|||
Widget buildSelectedAlbumName() {
|
||||
String text = "backup_controller_page_backup_selected".tr();
|
||||
final albums = ref
|
||||
.watch(backupAlbumProvider)
|
||||
.read(backupAlbumProvider)
|
||||
.where((album) => album.backupSelection == BackupSelection.selected)
|
||||
.toList();
|
||||
|
||||
|
|
@ -355,7 +355,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
|||
Widget buildExcludedAlbumName() {
|
||||
String text = "backup_controller_page_excluded".tr();
|
||||
final albums = ref
|
||||
.watch(backupAlbumProvider)
|
||||
.read(backupAlbumProvider)
|
||||
.where((album) => album.backupSelection == BackupSelection.excluded)
|
||||
.toList();
|
||||
|
||||
|
|
@ -403,6 +403,10 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
|||
trailing: ElevatedButton(
|
||||
onPressed: () async {
|
||||
await context.pushRoute(const DriftBackupAlbumSelectionRoute());
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentUser = ref.read(currentUserProvider);
|
||||
if (currentUser == null) {
|
||||
return;
|
||||
|
|
@ -562,6 +566,11 @@ class _PreparingStatusState extends ConsumerState {
|
|||
final currentUser = ref.read(currentUserProvider);
|
||||
if (currentUser != null && mounted) {
|
||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||
if (!context.mounted) {
|
||||
timer.cancel();
|
||||
_pollingTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop polling if processing count reaches 0
|
||||
final updatedProcessingCount = ref.read(driftBackupProvider.select((p) => p.processingCount));
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||
onPopInvokedWithResult: (didPop, _) async {
|
||||
if (!didPop) {
|
||||
await _handlePagePopped();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final user = ref.read(currentUserProvider);
|
||||
if (user == null) {
|
||||
|
|
@ -110,6 +113,10 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||
|
||||
final isBackupEnabled = SettingsRepository.instance.appConfig.backup.enabled;
|
||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(user.id);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
||||
final totalChanged = currentTotalAssetCount != _initialTotalAssetCount;
|
||||
final backupNotifier = ref.read(driftBackupProvider.notifier);
|
||||
|
|
@ -119,7 +126,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||
// Waits for hashing to be cancelled before starting a new one
|
||||
unawaited(nativeSync.cancelHashing().whenComplete(() => backgroundSync.hashAssets()));
|
||||
if (isBackupEnabled) {
|
||||
backupNotifier.stopForegroundBackup();
|
||||
backupNotifier.stopForegroundBackup(reason: "backup albums updated");
|
||||
unawaited(
|
||||
backgroundSync.syncRemote().then((success) {
|
||||
if (success) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
bool hasPopped = false;
|
||||
final previousBackup = ref.read(appConfigProvider).backup;
|
||||
final previousBackup = ref.watch(appConfigProvider.select((s) => s.backup));
|
||||
final previousCellularForVideos = previousBackup.useCellularForVideos;
|
||||
final previousCellularForPhotos = previousBackup.useCellularForPhotos;
|
||||
return PopScope(
|
||||
|
|
@ -59,7 +59,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
|||
|
||||
final backupNotifier = ref.read(driftBackupProvider.notifier);
|
||||
final backgroundSync = ref.read(backgroundSyncProvider);
|
||||
backupNotifier.stopForegroundBackup();
|
||||
backupNotifier.stopForegroundBackup(reason: "backup settings updated");
|
||||
unawaited(
|
||||
backgroundSync.syncRemote().then((success) {
|
||||
if (success) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'package:auto_route/auto_route.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
|
|
@ -13,11 +12,11 @@ import 'package:immich_mobile/routing/router.dart';
|
|||
import 'package:immich_mobile/services/immich_logger.service.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AppLogPage extends HookConsumerWidget {
|
||||
class AppLogPage extends HookWidget {
|
||||
const AppLogPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final immichLogger = LogService.I;
|
||||
final shouldReload = useState(false);
|
||||
final logMessages = useFuture(useMemoized(() => immichLogger.getMessages(), [shouldReload.value]));
|
||||
|
|
|
|||
|
|
@ -4,18 +4,18 @@ import 'package:auto_route/auto_route.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AppLogDetailPage extends HookConsumerWidget {
|
||||
class AppLogDetailPage extends HookWidget {
|
||||
const AppLogDetailPage({super.key, required this.logMessage});
|
||||
|
||||
final LogMessage logMessage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
Padding buildTextWithCopyButton(String header, String text) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class DownloadPanel extends ConsumerWidget {
|
|||
final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList();
|
||||
|
||||
void onCancelDownload(String id) {
|
||||
unawaited(ref.watch(downloadStateProvider.notifier).cancelDownload(id));
|
||||
unawaited(ref.read(downloadStateProvider.notifier).cancelDownload(id));
|
||||
}
|
||||
|
||||
return Positioned(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
|||
final headers = useState<List<SettingsHeader>>([]);
|
||||
final setInitialHeaders = useState(false);
|
||||
|
||||
final storedHeaders = ref.read(appConfigProvider).network.customHeaders;
|
||||
final storedHeaders = ref.watch(appConfigProvider.select((s) => s.network.customHeaders));
|
||||
if (!setInitialHeaders.value) {
|
||||
storedHeaders.forEach((k, v) {
|
||||
final header = SettingsHeader();
|
||||
|
|
@ -93,8 +93,9 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
|||
headersMap[key] = value;
|
||||
}
|
||||
|
||||
final apiService = ref.read(apiServiceProvider);
|
||||
await ref.read(settingsProvider).write(.networkCustomHeaders, headersMap);
|
||||
await ref.read(apiServiceProvider).updateHeaders();
|
||||
await apiService.updateHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -353,11 +353,11 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||
},
|
||||
onError: (exception) {
|
||||
log.severe('Failed to update auth info with access token: $accessToken');
|
||||
unawaited(ref.read(authProvider.notifier).logout());
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
unawaited(ref.read(authProvider.notifier).logout());
|
||||
unawaited(context.router.replaceAll([const LoginRoute()]));
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/widgets/forms/change_password_form.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ChangePasswordPage extends HookConsumerWidget {
|
||||
class ChangePasswordPage extends StatelessWidget {
|
||||
const ChangePasswordPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return const Scaffold(body: ChangePasswordForm());
|
||||
}
|
||||
Widget build(BuildContext context) => const Scaffold(body: ChangePasswordForm());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'dart:async';
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
|
@ -11,11 +10,11 @@ import 'package:immich_mobile/widgets/forms/login/login_form.dart';
|
|||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
@RoutePage()
|
||||
class LoginPage extends HookConsumerWidget {
|
||||
class LoginPage extends HookWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final appVersion = useState('0.0.0');
|
||||
|
||||
Future<void> getAppInfo() async {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:auto_route/auto_route.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
|
||||
|
|
@ -14,13 +13,13 @@ import 'package:immich_mobile/widgets/map/map_theme_override.dart';
|
|||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
|
||||
@RoutePage()
|
||||
class MapLocationPickerPage extends HookConsumerWidget {
|
||||
class MapLocationPickerPage extends HookWidget {
|
||||
final LatLng initialLatLng;
|
||||
|
||||
const MapLocationPickerPage({super.key, this.initialLatLng = const LatLng(0, 0)});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final selectedLatLng = useValueNotifier<LatLng>(initialLatLng);
|
||||
final controller = useRef<MapLibreMapController?>(null);
|
||||
final marker = useRef<Symbol?>(null);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class DownloadInfoPage extends ConsumerWidget {
|
|||
final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList();
|
||||
|
||||
void onCancelDownload(String id) {
|
||||
unawaited(ref.watch(downloadStateProvider.notifier).cancelDownload(id));
|
||||
unawaited(ref.read(downloadStateProvider.notifier).cancelDownload(id));
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class DriftActivitiesPage extends HookConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final activityNotifier = ref.read(albumActivityProvider((album.id, assetId)).notifier);
|
||||
final activityNotifier = ref.watch(albumActivityProvider((album.id, assetId)).notifier);
|
||||
final activities = ref.watch(albumActivityProvider((album.id, assetId)));
|
||||
final listViewScrollController = useScrollController();
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ class DriftAlbumOptionsPage extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
try {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ref.read(remoteAlbumProvider.notifier).addUsers(album.id, newUsers);
|
||||
ref.invalidate(remoteAlbumSharedUsersProvider(album.id));
|
||||
if (!context.mounted) {
|
||||
|
|
@ -137,7 +141,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget {
|
|||
|
||||
Widget buildOwnerInfo() {
|
||||
if (isOwner) {
|
||||
final owner = ref.watch(currentUserProvider);
|
||||
final owner = ref.read(currentUserProvider);
|
||||
return ListTile(
|
||||
leading: owner != null ? UserCircleAvatar(user: owner) : const SizedBox(),
|
||||
title: Text(album.ownerName, style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
|
|
@ -145,7 +149,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget {
|
|||
trailing: Text("owner", style: context.textTheme.labelLarge).t(context: context),
|
||||
);
|
||||
} else {
|
||||
final usersProvider = ref.watch(driftUsersProvider);
|
||||
final usersProvider = ref.read(driftUsersProvider);
|
||||
return usersProvider.maybeWhen(
|
||||
data: (users) {
|
||||
final user = users.firstWhereOrNull((u) => u.id == album.ownerId);
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
|||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftAssetSelectionTimelinePage extends ConsumerWidget {
|
||||
class DriftAssetSelectionTimelinePage extends StatelessWidget {
|
||||
final Set<BaseAsset> lockedSelectionAssets;
|
||||
const DriftAssetSelectionTimelinePage({super.key, this.lockedSelectionAssets = const {}});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
multiSelectProvider.overrideWith(
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ import 'package:immich_mobile/extensions/platform_extensions.dart';
|
|||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AssetTroubleshootPage extends ConsumerWidget {
|
||||
class AssetTroubleshootPage extends StatelessWidget {
|
||||
final BaseAsset asset;
|
||||
|
||||
const AssetTroubleshootPage({super.key, required this.asset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('asset_troubleshoot'.tr())),
|
||||
body: SingleChildScrollView(
|
||||
|
|
@ -29,13 +29,13 @@ class AssetTroubleshootPage extends ConsumerWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _AssetDetailsView extends ConsumerWidget {
|
||||
class _AssetDetailsView extends StatelessWidget {
|
||||
final BaseAsset asset;
|
||||
|
||||
const _AssetDetailsView({required this.asset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ import 'package:immich_mobile/widgets/map/map_thumbnail.dart';
|
|||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftLibraryPage extends ConsumerWidget {
|
||||
class DriftLibraryPage extends StatelessWidget {
|
||||
const DriftLibraryPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
|||
}
|
||||
|
||||
try {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ref.read(remoteAlbumProvider.notifier).addUsers(_album.id, newUsers);
|
||||
ref.invalidate(remoteAlbumSharedUsersProvider(_album.id));
|
||||
if (!context.mounted) {
|
||||
|
|
@ -133,6 +137,10 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
|||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ref.read(remoteAlbumProvider.notifier).deleteAlbum(_album.id);
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
|
|
@ -431,7 +439,7 @@ class _AlbumKebabMenu extends ConsumerWidget {
|
|||
|
||||
return FutureBuilder<bool>(
|
||||
future: ref
|
||||
.read(remoteAlbumServiceProvider)
|
||||
.watch(remoteAlbumServiceProvider)
|
||||
.getUserRole(album.id, user?.id ?? '')
|
||||
.then((role) => role == AlbumUserRole.editor),
|
||||
builder: (context, snapshot) {
|
||||
|
|
|
|||
|
|
@ -93,8 +93,8 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
|||
|
||||
if (asset.isImage) {
|
||||
_createTimer();
|
||||
} else if (ref.read(videoPlayerProvider(asset.heroTag)).status == VideoPlaybackStatus.paused) {
|
||||
unawaited(ref.read(videoPlayerProvider(asset.heroTag).notifier).play());
|
||||
} else if (ref.read(videoPlayerProvider(asset.id)).status == VideoPlaybackStatus.paused) {
|
||||
unawaited(ref.read(videoPlayerProvider(asset.id).notifier).play());
|
||||
} else {
|
||||
unawaited(_nextPage());
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
|||
final asset = widget.timeline.getAssetSafe(_index)!;
|
||||
|
||||
if (!asset.isImage) {
|
||||
unawaited(ref.read(videoPlayerProvider(asset.heroTag).notifier).pause());
|
||||
unawaited(ref.read(videoPlayerProvider(asset.id).notifier).pause());
|
||||
}
|
||||
|
||||
setState(() {
|
||||
|
|
@ -300,7 +300,7 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
|||
borderRadius: BorderRadius.zero,
|
||||
minHeight: 5,
|
||||
value:
|
||||
ref.watch(videoPlayerProvider(asset.heroTag).select((s) => s.position)).inMilliseconds /
|
||||
ref.read(videoPlayerProvider(asset.id).select((s) => s.position)).inMilliseconds /
|
||||
asset.duration.inMilliseconds,
|
||||
);
|
||||
}
|
||||
|
|
@ -374,13 +374,13 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
|||
builder: (context, value, _) => buildPhotoView(scale * (1.0 + value * _kenBurnsZoom)),
|
||||
);
|
||||
} else {
|
||||
final status = ref.watch(videoPlayerProvider(asset.heroTag).select((s) => s.status));
|
||||
final position = ref.read(videoPlayerProvider(asset.heroTag)).position;
|
||||
final status = ref.read(videoPlayerProvider(asset.id).select((s) => s.status));
|
||||
final position = ref.read(videoPlayerProvider(asset.id)).position;
|
||||
|
||||
if (status == VideoPlaybackStatus.completed && isCurrent && position.inMicroseconds > 0) {
|
||||
unawaited(_nextPage());
|
||||
} else if (status == VideoPlaybackStatus.playing) {
|
||||
unawaited(ref.read(videoPlayerProvider(asset.heroTag).notifier).setLoop(false));
|
||||
unawaited(ref.read(videoPlayerProvider(asset.id).notifier).setLoop(false));
|
||||
}
|
||||
|
||||
return PhotoView.customChild(
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class DriftUserSelectionPage extends HookConsumerWidget {
|
|||
body: suggestedShareUsers.widgetWhen(
|
||||
onData: (users) {
|
||||
// Get shared users for this album from the database
|
||||
final sharedUsers = ref.watch(remoteAlbumSharedUsersProvider(album.id));
|
||||
final sharedUsers = ref.read(remoteAlbumSharedUsersProvider(album.id));
|
||||
|
||||
return sharedUsers.when(
|
||||
data: (albumSharedUsers) {
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ class _AspectRatioSelector extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final editorState = ref.watch(editorStateProvider);
|
||||
final editorNotifier = ref.read(editorStateProvider.notifier);
|
||||
final editorNotifier = ref.watch(editorStateProvider.notifier);
|
||||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
|
|
@ -245,7 +245,7 @@ class _TransformControls extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final editorNotifier = ref.read(editorStateProvider.notifier);
|
||||
final editorNotifier = ref.watch(editorStateProvider.notifier);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -333,7 +333,7 @@ class _ResetEditsButton extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final editorState = ref.watch(editorStateProvider);
|
||||
final editorNotifier = ref.read(editorStateProvider.notifier);
|
||||
final editorNotifier = ref.watch(editorStateProvider.notifier);
|
||||
|
||||
return ImmichTextButton(
|
||||
labelText: 'reset'.tr(),
|
||||
|
|
@ -384,7 +384,7 @@ class _EditorPreviewState extends ConsumerState<_EditorPreview> with TickerProvi
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final editorState = ref.watch(editorStateProvider);
|
||||
final editorNotifier = ref.read(editorStateProvider.notifier);
|
||||
final editorNotifier = ref.watch(editorStateProvider.notifier);
|
||||
|
||||
ref.listen(editorStateProvider, (previous, current) {
|
||||
// Only re-apply the aspect ratio when it changes, otherwise the crop rect will shrink on every rotation
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/aspect_ratios.dart';
|
||||
import 'package:immich_mobile/domain/models/asset_edit.model.dart';
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/utils/editor.utils.dart';
|
||||
|
||||
part 'editor.provider.freezed.dart';
|
||||
|
||||
final editorStateProvider = NotifierProvider<EditorProvider, EditorState>(EditorProvider.new);
|
||||
|
||||
class EditorProvider extends Notifier<EditorState> {
|
||||
|
|
@ -32,8 +36,8 @@ class EditorProvider extends Notifier<EditorState> {
|
|||
final transform = normalizeTransformEdits(edits);
|
||||
|
||||
state = state.copyWith(
|
||||
originalWidth: originalWidth,
|
||||
originalHeight: originalHeight,
|
||||
originalWidth: originalWidth ?? state.originalWidth,
|
||||
originalHeight: originalHeight ?? state.originalHeight,
|
||||
crop: crop,
|
||||
flipHorizontal: transform.mirrorHorizontal,
|
||||
flipVertical: transform.mirrorVertical,
|
||||
|
|
@ -106,104 +110,24 @@ class EditorProvider extends Notifier<EditorState> {
|
|||
}
|
||||
}
|
||||
|
||||
class EditorState {
|
||||
final bool isApplyingEdits;
|
||||
@freezed
|
||||
abstract class EditorState with _$EditorState {
|
||||
const EditorState._();
|
||||
|
||||
final int rotationAngle;
|
||||
final bool flipHorizontal;
|
||||
final bool flipVertical;
|
||||
final Rect crop;
|
||||
final CropAspectRatio aspectRatio;
|
||||
|
||||
final int originalWidth;
|
||||
final int originalHeight;
|
||||
|
||||
final Duration animationDuration;
|
||||
|
||||
final bool hasUnsavedEdits;
|
||||
|
||||
const EditorState({
|
||||
bool? isApplyingEdits,
|
||||
int? rotationAngle,
|
||||
bool? flipHorizontal,
|
||||
bool? flipVertical,
|
||||
Rect? crop,
|
||||
CropAspectRatio? aspectRatio,
|
||||
int? originalWidth,
|
||||
int? originalHeight,
|
||||
Duration? animationDuration,
|
||||
bool? hasUnsavedEdits,
|
||||
}) : isApplyingEdits = isApplyingEdits ?? false,
|
||||
rotationAngle = rotationAngle ?? 0,
|
||||
flipHorizontal = flipHorizontal ?? false,
|
||||
flipVertical = flipVertical ?? false,
|
||||
animationDuration = animationDuration ?? Duration.zero,
|
||||
originalWidth = originalWidth ?? 0,
|
||||
originalHeight = originalHeight ?? 0,
|
||||
crop = crop ?? const Rect.fromLTRB(0, 0, 1, 1),
|
||||
aspectRatio = aspectRatio ?? CropAspectRatio.free,
|
||||
hasUnsavedEdits = hasUnsavedEdits ?? false;
|
||||
|
||||
EditorState copyWith({
|
||||
bool? isApplyingEdits,
|
||||
int? rotationAngle,
|
||||
bool? flipHorizontal,
|
||||
bool? flipVertical,
|
||||
CropAspectRatio? aspectRatio,
|
||||
int? originalWidth,
|
||||
int? originalHeight,
|
||||
Duration? animationDuration,
|
||||
Rect? crop,
|
||||
bool? hasUnsavedEdits,
|
||||
}) {
|
||||
return EditorState(
|
||||
isApplyingEdits: isApplyingEdits ?? this.isApplyingEdits,
|
||||
rotationAngle: rotationAngle ?? this.rotationAngle,
|
||||
flipHorizontal: flipHorizontal ?? this.flipHorizontal,
|
||||
flipVertical: flipVertical ?? this.flipVertical,
|
||||
aspectRatio: aspectRatio ?? this.aspectRatio,
|
||||
animationDuration: animationDuration ?? this.animationDuration,
|
||||
originalWidth: originalWidth ?? this.originalWidth,
|
||||
originalHeight: originalHeight ?? this.originalHeight,
|
||||
crop: crop ?? this.crop,
|
||||
hasUnsavedEdits: hasUnsavedEdits ?? this.hasUnsavedEdits,
|
||||
);
|
||||
}
|
||||
const factory EditorState({
|
||||
@Default(false) bool isApplyingEdits,
|
||||
@Default(0) int rotationAngle,
|
||||
@Default(false) bool flipHorizontal,
|
||||
@Default(false) bool flipVertical,
|
||||
@Default(Rect.fromLTRB(0, 0, 1, 1)) Rect crop,
|
||||
@Default(CropAspectRatio.free) CropAspectRatio aspectRatio,
|
||||
@Default(0) int originalWidth,
|
||||
@Default(0) int originalHeight,
|
||||
@Default(Duration.zero) Duration animationDuration,
|
||||
@Default(false) bool hasUnsavedEdits,
|
||||
}) = _EditorState;
|
||||
|
||||
bool get hasEdits {
|
||||
return rotationAngle != 0 || flipHorizontal || flipVertical || crop != const Rect.fromLTRB(0, 0, 1, 1);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other is EditorState &&
|
||||
other.isApplyingEdits == isApplyingEdits &&
|
||||
other.rotationAngle == rotationAngle &&
|
||||
other.flipHorizontal == flipHorizontal &&
|
||||
other.flipVertical == flipVertical &&
|
||||
other.crop == crop &&
|
||||
other.aspectRatio == aspectRatio &&
|
||||
other.originalWidth == originalWidth &&
|
||||
other.originalHeight == originalHeight &&
|
||||
other.animationDuration == animationDuration &&
|
||||
other.hasUnsavedEdits == hasUnsavedEdits;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return isApplyingEdits.hashCode ^
|
||||
rotationAngle.hashCode ^
|
||||
flipHorizontal.hashCode ^
|
||||
flipVertical.hashCode ^
|
||||
crop.hashCode ^
|
||||
aspectRatio.hashCode ^
|
||||
originalWidth.hashCode ^
|
||||
originalHeight.hashCode ^
|
||||
animationDuration.hashCode ^
|
||||
hasUnsavedEdits.hashCode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,10 @@ class _ProfilePictureCropPageState extends ConsumerState<ProfilePictureCropPage>
|
|||
final croppedImage = await _cropController.croppedImage();
|
||||
final pngBytes = await imageToUint8List(croppedImage);
|
||||
final xFile = XFile.fromData(pngBytes, mimeType: 'image/png');
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final success = await ref
|
||||
.read(uploadProfileImageProvider.notifier)
|
||||
.upload(xFile, fileName: 'profile-picture.png');
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue