UPDATE 20260729 : There are EXIF filters in workflows now!
Put your media in the correct directories using workflows.
Below the regex for a Samsung phone
Image
^\d{8}_\d{6}\.(jpg|jpeg|heic|png)$
Video
^\d{8}_\d{6}\.(mp4|mov|mkv)$
WhatsApp Image
^IMG-\d{8}-WA\d+\.(jpg|jpeg|png|webp)$
WhatsApp Video
^VID-\d{8}-WA\d+\.(mp4|3gp)$
Signal Image
^(signal-|Signal-).*\.(jpg|jpeg|png|webp)$
Signal Video
^(signal-|Signal-).*\.(mp4|mov)$
When ingesting WhatsApp media, the dates in the database will contain the ingest date. This is because the GPS/Date and other exif information are removed from the Media in WhatsApp.
NOTES:
Always import your camera media first, these will contain all exif info, if you upload WhatsApp media containing the same image it can be skipped. (Look for deduplication tip below)
WhatsApp autouploaded using the App on your phone rarely needs adjusting. (Taking a photo and uploading it the same day will fix the wrong day issue)
Luckily the WhatsApp media contains the date in the filename.
This software will connect to your immich instance, searches for ingestdates and whatsapp filenames discrepancies. And wil fix these in the immich database.
I’ve got a directory containing above code for every user, with their own .env file, and custom filters
Sometimes media has a incorrect GPS location, or it is missing, or as above set as 0:0
You CAN change the location of Images using the MAP in Immich. (Select MAP > Day or image > Menu: Change location) (Also under Utilities) Immich WILL NOT change your image!, It will write a sidecar file with updated location info.
How I like to fix this: Download the images for which you want to remove the GPS information. Delete from Immich. Run below script over those images to remove Exif information and reupload.
exiftool -gps:all= FILENAME
Loads of the same images
Deduplicate? : Use Utilities > Review duplicates
Select camera instead of WhatsApp image to keep. (Most of the time bigger and has all exif information!)
Burst photos or simular photos? Use Stacking. This will show only ONE thumbnail in albums/timeline.
Manage people data in bulk : Options to update people data in bulk, and with advance filters
People Merge Suggestion : Option to bulk merge people with suggested faces based on similarity.
Update Missing Locations : Find assets in your library those are without location and update them with the location of the asset.
Potential Albums : Find albums that are potential to be created based on the assets and people in your library.
Analytics : Get analytics on your library like assets over time, exif data, etc.
Smart Search : Search your library with natural language, supports queries like “show me all my photos from 2024 of “
Bulk Date Offset : Offset the date of selected assets by a given amount of time. Majorly used to fix the date of assets that are out of sync with the actual date.
PYTHON script to download an album (with a filename filter)
NOTE: At the bottom you can remove the # comments to also REMOVE from immich
import requests
import os
IMMICH_URL = "http://192.168.1.2:2283/api"
API_KEY = "2Nk4sO4eEm001Cm1Dsnl3XXXXXXXXXXXXXXX"
ALBUM_ID = "c4ce0661-0c4c-4c49-b6c1-XXXXXXXXXXXXXXXXXXXXX"
FILENAME_PREFIX = "VID_" # filename filter
HEADERS = {
"x-api-key": API_KEY
}
DOWNLOAD_DIR = "./downloaded"
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
def get_album_assets(album_id):
r = requests.get(
f"{IMMICH_URL}/albums/{album_id}",
headers=HEADERS
)
r.raise_for_status()
return r.json()["assets"]
def filter_assets(assets):
# simulate SQL LIKE 'IMG_2023%'
return [
a for a in assets
if a["originalFileName"].startswith(FILENAME_PREFIX)
]
def download_asset(asset):
asset_id = asset["id"]
filename = asset["originalFileName"]
url = f"{IMMICH_URL}/assets/{asset_id}/original"
r = requests.get(url, headers=HEADERS, stream=True)
r.raise_for_status()
path = os.path.join(DOWNLOAD_DIR, filename)
with open(path, "wb") as f:
for chunk in r.iter_content(8192):
f.write(chunk)
return path
def delete_assets(asset_ids):
r = requests.delete(
f"{IMMICH_URL}/assets",
headers=HEADERS,
json={"ids": asset_ids}
)
r.raise_for_status()
def main():
print("Fetching album assets...")
assets = get_album_assets(ALBUM_ID)
print(f"Total assets in album: {len(assets)}")
print("Filtering by filename...")
filtered = filter_assets(assets)
print(f"Matched assets: {len(filtered)}")
downloaded = []
print("Downloading...")
for asset in filtered:
try:
path = download_asset(asset)
downloaded.append((asset["id"], path))
except Exception as e:
print(f"Download failed: {asset['id']} - {e}")
# VERIFY
print("Verifying...")
if len(downloaded) != len(filtered):
print("Download mismatch. Abort delete.")
return
for _, path in downloaded:
if not os.path.exists(path) or os.path.getsize(path) == 0:
print(f"Invalid file: {path}")
return
print("Verification OK")
# DELETE
ids_to_delete = [asset_id for asset_id, _ in downloaded]
#print("Deleting assets...")
#delete_assets(ids_to_delete)
print("Done!")
if __name__ == "__main__":
main()
Got a question, could I make the video viewer also for images.
Well, that is a great idea, i’ve got some panoramic photos myself.
A little modification, some added code, but here is a working example.
Some vacation pictures widescreen …
CODE imageview.py filename Use esc to stop, and enter for next image. (Has better full screen experience than my movie player. (no padding) have to revisit that one )
Nice to have?
Back button?
Comments, renaming thumb
from pathlib import Path
from sys import platform as PLATFORM
import os
import re
import PySimpleGUI as sg
from PIL import Image, ImageEnhance, ImageTk, ImageOps, ImageFilter
from xml.etree import ElementTree as ET
import sys
from sys import platform as PLATFORM
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)
try:
image=sys.argv[1]
except:
print(sys.argv[0] + " filename")
exit()
def nextFile(currentfile,dir):
newfile=""
dirpath = os.path.dirname(dir)
fileList = []
for f in os.listdir(dirpath):
#fpath = os.path.join(dirpath, f)
fpath = f
if os.path.isfile(fpath) and f.endswith(('.jpg','.JPG')):
fileList.append(fpath)
fileList.sort()
for i in range(len(fileList)):
try:
if (fileList[i]) == currentfile:
newfile=fileList[i+1]
break
except:
newfile=fileList[0]
return newfile
# yeah i know .. no thumb but full image, change it yourself!
def loadthumb(thumbfile):
# IF exists
path_to_file = thumbfile
path = Path(path_to_file)
if path.is_file():
im = Image.open(thumbfile)
im=ImageOps.contain(im, (5760,5760))
thumbimage = ImageTk.PhotoImage(image=im)
window['image'].update(data=thumbimage)
else:
window['image'].update("")
sg.theme('SystemDefaultForReal')
#------- Layout image only --------#
layout = [
[[sg.Image('', size=(5760, 1080), key='image',background_color='black',pad=(0, 0))],
]]
#------- Set window --------#
window = sg.Window('Triple image player', layout, no_titlebar=True, margins=(0,0),location=(0,0), size=(5760,1080), keep_on_top=True, finalize=True,resizable=False)
window.bring_to_front()
window.Maximize()
window.bind("<Escape>", "-ESCAPE-")
window.bind("<Return>", "-ENTER-")
window['image'].expand(True, True)
loadthumb(image)
nextfile = image
#------------ The Event Loop ------------#
while True:
event, values = window.read(timeout=1000) # run with a timeout so that current location can be updated
if event == sg.WIN_CLOSED:
break
if event == '-ENTER-':
nextfile = nextFile(nextfile,'./')
loadthumb(nextfile)
if event == '-ESCAPE-':
window.close()
window.close()
Converting images for right resolution from a temp directory filled with large panorama photos
ls temp | while read; do
convert -resize 5760x -gravity center -crop 5760x1080 -auto-orient "temp/$REPLY" "$REPLY"
done
Mostly I use kdenlive, but if you have an actioncam which also records movement (pan, tilt, zoom, and rotation) use a tool like Gyroflow! This will use the motion data to correct the movement!
I was playing around with Phantomjs a headless browser. Using this as a scraper for a ajax enabled site.
After scraping a wallpaper site, I wanted to take the big pictures and display these as background.
First sort and resize to a better size.
Below does the following:
Image width larger than 1800 AND
Image height larger than 900
Resize to 1920×1080 ( enlarge or reduce size )
Not screen filling (portrait mode) ? Than add black bars on the side.
Place the image in wallpaper directory
Convert script, if you resize huge images beforehand, you safe cpu resources later. You also can place other colors or even another background instead of black.
mkdir -p wallpaper
ls * | while read ; do
info=$(identify "$REPLY" | awk '{ print $3 }' 2>/dev/null)
height=$( echo $info | cut -f2 -dx)
width=$( echo $info | cut -f1 -dx)
if [ $width -gt 1800 ] && [ $height -gt 900 ] ; then
convert -resize 1920x1080 -gravity center -background black -extent 1920x1080 "$REPLY" "wallpaper/$REPLY"
fi
done
Set a random wallpaper as background using cron.
#!/bin/bash
DISPLAY=":0.0"
XAUTHORITY="/home/henri/.Xauthority"
XDG_RUNTIME_DIR="/run/user/1000"
cd /home/henri/Pictures/
getranpic=$(ls wallpaper/ |sort -R |tail -1)
#gsettings set org.gnome.desktop.background picture-options 'wallpaper'
#Set different modes ( enum 'none' 'wallpaper' 'centered' 'scaled' 'stretched' 'zoom' 'spanned' )
gsettings set org.gnome.desktop.background picture-uri-dark "$(realpath wallpaper/$getranpic)"
logger "$(realpath $getranpic)"
They all have their benefits. But I want the impossible. One with all the benefits. We choose the bag depending on the occasion, but for example a large bag is perfect for a far away vacation, sometimes we do daytrips which can be done with a smaller bag. And I want an even smaller bag when going to the city to get something to eat when on holiday. Or the day trips to family or other cities in our country.
Also the means of transport matters.
Bike: Small one Own car: Big bag and a small one Plane: We have been all over the world, it depends on travel and transport in those countries.
I LOVE the ones with easy access. (Slingshot and mindshift) Some of them have a way to take your tripod with you.
Sometimes we have to take two camera’s with us, so we don’t fight. (Vertex and Mindshift take two)
Lowepro Vertex 200 AW
Comes with a tripod holder, lots of pockets and easy adjustable. Plus it has a rain cover hidden in the bottom.
Hama
A real small bag. This one we use for day trips. Holds camera plus a lens or our small binoculars.
Lowepro Slingshot Edge
This is another day trip bag. For when we need a little more room or take some food with us. Love the sling concept, but not much used anymore.
Mindshift Gear Rotation 180 Horizon
I love this bag! We took this one with us to Peru and Bolivia. Easy on the shoulders and back. I carried this one the whole day for several weeks. The camera sling rests on your hips.
What we take with us in the big bags?
Filters: Polarisation/8x Stop filters + ring adaptors
My little screen magnifier
Many Sdcards
WD Passport for backup (see the New Zealand hack)
Multiple lenses
Cleaning material
Lens sunshades
Mini tripod
GPS tracker (To embed locations in photos)
Raincoat
Technochanter 🙂
"If something is worth doing, it's worth overdoing."