mirror of
https://github.com/immich-app/immich.git
synced 2024-12-26 10:50:29 +02:00
docs: community projects (#8641)
This commit is contained in:
parent
327b9bd59c
commit
9e122764e7
@ -20,10 +20,6 @@ In any other situation, there are 3 different options that can appear:
|
||||
|
||||
- OFFLINE PATHS - These files are the result of manually deleting files in the upload library or a failed file move in the past (losing track of a file).
|
||||
|
||||
:::tip
|
||||
To get rid of Offline paths you can follow this [guide](/docs/guides/remove-offline-files.md)
|
||||
:::
|
||||
|
||||
- UNTRACKED FILES - These files are not tracked by the application. They can be the result of failed moves, interrupted uploads, or left behind due to a bug.
|
||||
|
||||
In addition, you can download the information from a page, mark everything (in order to check hashing) and correct the problem if a match is found in the hashing.
|
||||
|
12
docs/docs/community-projects.mdx
Normal file
12
docs/docs/community-projects.mdx
Normal file
@ -0,0 +1,12 @@
|
||||
# Community Projects
|
||||
|
||||
This page lists community projects that are built around Immich, but not officially supported by the development team.
|
||||
|
||||
:::warning
|
||||
This list comes with no guarantees about security, performance, reliability, or accuracy. Use at your own risk.
|
||||
:::
|
||||
|
||||
import CommunityProjects from '../src/components/community-projects.tsx';
|
||||
import React from 'react';
|
||||
|
||||
<CommunityProjects />
|
@ -1,130 +0,0 @@
|
||||
# API Album Sync (Python Script)
|
||||
|
||||
This is an example of a python script for syncing an album to a local folder. This was used for a digital photoframe so the displayed photos could be managed from the immich web or app UI.
|
||||
|
||||
The script is copied below in it's current form. A repository is hosted [here](https://git.orenit.solutions/open/immichalbumpull).
|
||||
|
||||
:::danger
|
||||
This guide uses a generated API key. This key gives the same access to your immich instance as the user it is attached to, so be careful how the config file is stored and transferred.
|
||||
:::
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.7+
|
||||
- [requests library](https://pypi.org/project/requests/)
|
||||
|
||||
### Installing
|
||||
|
||||
Copy the contents of 'pull.py' (shown below) to your chosen location or clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://git.orenit.solutions/open/immichalbumpull
|
||||
```
|
||||
|
||||
Edit or create the 'config.ini' file in the same directory as the script with the necessary details:
|
||||
|
||||
```ini title='config.ini'
|
||||
[immich]
|
||||
# URL of target immich instance
|
||||
url = https://photo.example.com
|
||||
# API key from Account Settings -> API Keys
|
||||
apikey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
# Full local path to target directory
|
||||
destination = /home/photo/photos
|
||||
# immich album name
|
||||
album = Photoframe
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Run the script directly:
|
||||
|
||||
```bash
|
||||
./pull.py
|
||||
```
|
||||
|
||||
Or from cron (every 5 minutes):
|
||||
|
||||
```bash
|
||||
*/5 * * * * /usr/bin/python /home/user/immichalbumpull/pull.py
|
||||
```
|
||||
|
||||
### Python Script
|
||||
|
||||
```python title='pull.py'
|
||||
#!/usr/bin/env python
|
||||
|
||||
import requests
|
||||
import configparser
|
||||
import os
|
||||
import shutil
|
||||
|
||||
# Read config file
|
||||
config = configparser.ConfigParser()
|
||||
config.read('config.ini')
|
||||
|
||||
url = config['immich']['url']
|
||||
apikey = config['immich']['apikey']
|
||||
photodir = config['immich']['destination']
|
||||
albumname = config['immich']['album']
|
||||
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'x-api-key': apikey
|
||||
}
|
||||
|
||||
# Set up the directory for the downloaded images
|
||||
os.makedirs(photodir, exist_ok=True)
|
||||
|
||||
# Get the list of albums from the API
|
||||
response = requests.get(url + "/api/album", headers=headers)
|
||||
|
||||
# Parse the JSON response
|
||||
data = response.json()
|
||||
|
||||
# Find the chosen album id
|
||||
for item in data:
|
||||
if item['albumName'] == albumname:
|
||||
albumid = item['id']
|
||||
|
||||
# Get the list of photos from the API using the albumid
|
||||
response = requests.get(url + "/api/album/" + albumid, headers=headers)
|
||||
|
||||
# Parse the JSON response and extract the URLs of the images
|
||||
data = response.json()
|
||||
image_urls = data['assets']
|
||||
|
||||
# Download each image from the URL and save it to the directory
|
||||
headers = {
|
||||
'Accept': 'application/octet-stream',
|
||||
'x-api-key': apikey
|
||||
}
|
||||
|
||||
photolist = []
|
||||
|
||||
for id in image_urls:
|
||||
# Query asset info endpoint for correct extension
|
||||
assetinfourl = url + "/api/asset/" + str(id['id'])
|
||||
response = requests.get(assetinfourl, headers=headers)
|
||||
assetinfo = response.json()
|
||||
ext = os.path.splitext(assetinfo['originalFileName'])
|
||||
|
||||
asseturl = url + "/api/download/asset/" + str(id['id'])
|
||||
response = requests.post(asseturl, headers=headers, stream=True)
|
||||
|
||||
# Build current photo list for deletions below
|
||||
photo = os.path.basename(asseturl) + ext[1]
|
||||
photolist.append(photo)
|
||||
|
||||
photofullpath = photodir + '/' + os.path.basename(asseturl) + ext[1]
|
||||
# Only download file if it doesn't already exist
|
||||
if not os.path.exists(photofullpath):
|
||||
with open(photofullpath, 'wb') as f:
|
||||
for chunk in response.iter_content(1024):
|
||||
f.write(chunk)
|
||||
|
||||
# Delete old photos removed from album
|
||||
for filename in os.listdir(photodir):
|
||||
if filename not in photolist:
|
||||
os.unlink(os.path.join(photodir, filename))
|
||||
```
|
@ -1,176 +0,0 @@
|
||||
# Remove Offline Files [Community]
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
:::note
|
||||
**Before running the script**, please make sure you have a [backup](/docs/administration/backup-and-restore) of your assets and database.
|
||||
:::
|
||||
|
||||
:::info
|
||||
**None** of the scripts can delete orphaned files from the external library.
|
||||
:::
|
||||
|
||||
This page is a guide to get rid of offline files from the repair page.
|
||||
|
||||
<Tabs>
|
||||
|
||||
<TabItem value="Python script (Best way)" label="Python script (Best way)">
|
||||
|
||||
This way works by retrieving a file that contains a list of all the files that are defined as offline files, running a script that uses the [Immich API](/docs/api/delete-assets) in order to remove the offline files.
|
||||
|
||||
1. Create an API key under Admin User -> Account Settings -> API Keys -> New API Key -> Copy to clipboard.
|
||||
2. Copy and save the code to file -> `Immich Remove Offline Files.py`.
|
||||
3. Run the script and follow the instructions.
|
||||
|
||||
:::note
|
||||
You might need to run `pip install halo tabulate tqdm` if these dependencies are missing on your machine.
|
||||
:::
|
||||
|
||||
```bash title='Python'
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Note: you might need to run "pip install halo tabulate tqdm" if these dependencies are missing on your machine
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import requests
|
||||
|
||||
from datetime import datetime
|
||||
from halo import Halo
|
||||
from tabulate import tabulate
|
||||
from tqdm import tqdm
|
||||
from urllib.parse import urlparse
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(description='Fetch file report and delete orphaned media assets from Immich.')
|
||||
parser.add_argument('--apikey', help='Immich API key for authentication')
|
||||
parser.add_argument('--immichaddress', help='Full address for Immich, including protocol and port')
|
||||
parser.add_argument('--no_prompt', action='store_true', help='Delete orphaned media assets without confirmation')
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
def filter_entities(response_json, entity_type):
|
||||
return [
|
||||
{'pathValue': entity['pathValue'], 'entityId': entity['entityId'], 'entityType': entity['entityType']}
|
||||
for entity in response_json.get('orphans', []) if entity.get('entityType') == entity_type
|
||||
]
|
||||
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
try:
|
||||
if args.apikey:
|
||||
api_key = args.apikey
|
||||
else:
|
||||
api_key = input('Enter the Immich API key: ')
|
||||
|
||||
if args.immichaddress:
|
||||
immich_server = args.immichaddress
|
||||
else:
|
||||
immich_server = input('Enter the full web address for Immich, including protocol and port: ')
|
||||
immich_parsed_url = urlparse(immich_server)
|
||||
base_url = f'{immich_parsed_url.scheme}://{immich_parsed_url.netloc}'
|
||||
api_url = f'{base_url}/api'
|
||||
file_report_url = api_url + '/audit/file-report'
|
||||
headers = {'x-api-key': api_key}
|
||||
|
||||
print()
|
||||
spinner = Halo(text='Retrieving list of orphaned media assets...', spinner='dots')
|
||||
spinner.start()
|
||||
|
||||
try:
|
||||
response = requests.get(file_report_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
spinner.succeed('Success!')
|
||||
except requests.exceptions.RequestException as e:
|
||||
spinner.fail(f'Failed to fetch assets: {str(e)}')
|
||||
|
||||
person_assets = filter_entities(response.json(), 'person')
|
||||
orphan_media_assets = filter_entities(response.json(), 'asset')
|
||||
|
||||
num_entries = len(orphan_media_assets)
|
||||
|
||||
if num_entries == 0:
|
||||
print('No orphaned media assets found; exiting.')
|
||||
return
|
||||
|
||||
else:
|
||||
if not args.no_prompt:
|
||||
table_data = []
|
||||
for asset in orphan_media_assets:
|
||||
table_data.append([asset['pathValue'], asset['entityId']])
|
||||
print(tabulate(table_data, headers=['Path Value', 'Entity ID'], tablefmt='pretty'))
|
||||
print()
|
||||
|
||||
if person_assets:
|
||||
print('Found orphaned person assets! Please run the "RECOGNIZE FACES > ALL" job in Immich after running this tool to correct this.')
|
||||
print()
|
||||
|
||||
if num_entries > 0:
|
||||
summary = f'There {"is" if num_entries == 1 else "are"} {num_entries} orphaned media asset{"s" if num_entries != 1 else ""}. Would you like to delete {"them" if num_entries != 1 else "it"} from Immich? (yes/no): '
|
||||
user_input = input(summary).lower()
|
||||
print()
|
||||
|
||||
if user_input not in ('y', 'yes'):
|
||||
print('Exiting without making any changes.')
|
||||
return
|
||||
|
||||
with tqdm(total=num_entries, desc="Deleting orphaned media assets", unit="asset") as progress_bar:
|
||||
for asset in orphan_media_assets:
|
||||
entity_id = asset['entityId']
|
||||
asset_url = f'{api_url}/asset'
|
||||
delete_payload = json.dumps({'force': True, 'ids': [entity_id]})
|
||||
headers = {'Content-Type': 'application/json', 'x-api-key': api_key}
|
||||
response = requests.delete(asset_url, headers=headers, data=delete_payload)
|
||||
response.raise_for_status()
|
||||
progress_bar.set_postfix_str(entity_id)
|
||||
progress_bar.update(1)
|
||||
print()
|
||||
print('Orphaned media assets deleted successfully!')
|
||||
except Exception as e:
|
||||
print()
|
||||
print(f"An error occurred: {str(e)}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
Thanks to [DooMRunneR](https://discord.com/channels/979116623879368755/1179655214870040596/1194308198413373482) and [Sircharlo](https://discord.com/channels/979116623879368755/1179655214870040596/1195038609812758639) for writing this script.
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="Bash and PowerShell script" label="Bash and PowerShell script" default>
|
||||
|
||||
This way works by downloading a JSON file that contains a list of all the files that are defined as offline files, running a script that uses the [Immich API](/docs/api/delete-assets) in order to remove the offline files.
|
||||
|
||||
1. Create an API key under Admin User -> Account Settings -> API Keys -> New API Key -> Copy to clipboard.
|
||||
2. Download the JSON file under Administration -> repair -> Export.
|
||||
3. Replace `YOUR_IP_HERE` and `YOUR_API_KEY_HERE` with your actual IP address and API key in the script.
|
||||
4. Run the script in the same folder where the JSON file is located.
|
||||
|
||||
## Script for Linux based systems:
|
||||
|
||||
```bash title='Bash'
|
||||
awk -F\" '/entityId/ {print $4}' orphans.json | while read line; do curl --location --request DELETE 'http://YOUR_IP_HERE:2283/api/asset' --header 'Content- Type: application/json' --header 'x-api-key: YOUR_API_KEY_HERE' --data '{ "force": true, "ids": ["'"$line"'"]}';done
|
||||
```
|
||||
|
||||
## Script for the Windows system (run through PowerShell):
|
||||
|
||||
```powershell title='PowerShell'
|
||||
Get-Content orphans.json | Select-String -Pattern 'entityId' | ForEach-Object {
|
||||
$line = $_ -split '"' | Select-Object -Index 3
|
||||
$body = [pscustomobject]@{
|
||||
'ids' = @($line)
|
||||
'force' = (' true ' | ConvertFrom-Json)
|
||||
} | ConvertTo-Json -Depth 3
|
||||
Invoke-RestMethod -Uri 'http://YOUR_IP_HERE:2283/api/asset' -Method Delete -Headers @{
|
||||
'Content-Type' = 'application/json'
|
||||
'x-api-key' = 'YOUR_API_KEY_HERE'
|
||||
} -Body $body
|
||||
}
|
||||
```
|
||||
|
||||
Thanks to [DooMRunneR](https://discord.com/channels/979116623879368755/1179655214870040596/1194308198413373482) for writing this script.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
66
docs/src/components/community-projects.tsx
Normal file
66
docs/src/components/community-projects.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import Link from '@docusaurus/Link';
|
||||
import React from 'react';
|
||||
|
||||
interface CommunityProjectProps {
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const projects: CommunityProjectProps[] = [
|
||||
{
|
||||
title: 'immich-go',
|
||||
description: `An alternative to the immich-CLI command that doesn't depend on nodejs installation. It tries its best for importing google photos takeout archives.`,
|
||||
url: 'https://github.com/simulot/immich-go',
|
||||
},
|
||||
{
|
||||
title: 'ImmichFrame',
|
||||
description: 'Run an Immich slideshow in a photo frame.',
|
||||
url: 'https://github.com/3rob3/ImmichFrame',
|
||||
},
|
||||
{
|
||||
title: 'API Album Sync',
|
||||
description: 'A python script to sync folders as albums.',
|
||||
url: 'https://git.orenit.solutions/open/immichalbumpull',
|
||||
},
|
||||
{
|
||||
title: 'Remove offline files',
|
||||
description: 'A python script to remove offline files.',
|
||||
url: 'https://gist.github.com/Thoroslives/ca5d8e1efd15111febc1e7b34ac72668',
|
||||
},
|
||||
];
|
||||
|
||||
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
||||
return (
|
||||
<section className="flex flex-col gap-4 justify-between dark:bg-immich-dark-gray bg-immich-gray dark:border-0 border-gray-200 border border-solid rounded-2xl p-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="m-0 items-start flex gap-2">
|
||||
<span>{title}</span>
|
||||
</p>
|
||||
|
||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300">{description}</p>
|
||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300">
|
||||
<a href={url}>{url}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Link
|
||||
className="px-4 py-2 bg-immich-primary/10 dark:bg-gray-300 rounded-full hover:no-underline text-immich-primary dark:text-immich-dark-bg font-bold uppercase"
|
||||
to={url}
|
||||
>
|
||||
View Project
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CommunityProjects(): JSX.Element {
|
||||
return (
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||
{projects.map((project) => (
|
||||
<CommunityProject {...project} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
2
docs/static/_redirects
vendored
2
docs/static/_redirects
vendored
@ -26,3 +26,5 @@
|
||||
/docs/guides/machine-learning /docs/guides/remote-machine-learning 301
|
||||
/docs/administration/password-login /docs/administration/system-settings 301
|
||||
/docs/features/search /docs/features/smart-search 301
|
||||
/docs/guides/api-album-sync /docs/community-projects 301
|
||||
/docs/guides/remove-offline-files /docs/community-projects 301
|
||||
|
Loading…
Reference in New Issue
Block a user