Category Archives: Photography

Immich hints and tips part 2

FIX rotation of movie

usage rotatefix movie001.mpg 90 (or -90)

If your data is in a external library, you can do this on the file in the library. After that you have to select it and do the following:

  • Refresh encoded videos
  • Refresh metadata
  • Refresh thumbnails
#!/bin/bash
#set -x
if [ "$#" -lt 2 ]; then
  echo >&2 'fix filename 90||-90||0'
  exit 1
fi

fix=0
exiftool $1  | grep "^Create Date" | grep 0000 && fix=1 

dater=$(realpath $1| grep -oE '[0-9]{8}' | head -1 )
if [ $2 == "90" ] ; then
	ffmpeg -i $1  -vf 'transpose=1' -c:a copy  tmp_${1} 
mv tmp_${1} $1
fi
if [ $2 == "-90" ] ; then
	ffmpeg -i $1  -vf 'transpose=2' -c:a copy  tmp_${1} 
mv tmp_${1} $1
fi
if [ $fix == "1" ] ; then
	echo "Fixing date"
	touch -t ${dater}1200.00 $1
fi

Get location of file by pressing the (i)

Deinterlace video and touch date, when no exif date is embedded

#!/bin/bash

mkdir -p deinterlace

for video in DV*; do
    [ -f "$video" ] || continue

    : >2.log
    echo "testing $video"

    ffmpeg -i "$video" -filter:v idet -frames:v 500 -an -f rawvideo -y /dev/null 2>2.log

    inter=$(
        grep Parsed 2.log |
        tail -2 |
        cut -d: -f2- |
        awk '
        {
            inter += $2 + $4
            prog += $6
        }
        END {
            print (inter > prog ? 1 : 0)
        }'
    )

    if [ "$inter" -eq 1 ]; then
        echo "convert $video"
         ffmpeg -i "$video" -vf bwdif -c:v libx264 -crf 18 -preset medium \
             -c:a aac -b:a 192k "deinterlace/${video%.*}-deinterlaced.mp4"
    fi

    dt=$(echo "$video" | grep -oE '[0-9]{2}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}' | tr '_' ' ' | tr '-' ' ')

if [ -n "$dt" ]; then
    # convert DD-MM-YY HH-MM to YYYY-MM-DD HH:MM
    read year month day hour minute <<< "$dt"

    # assume 20xx for year
    year=$((2000 + year))

    touch -d "$year-$month-$day $hour:$minute:00" "deinterlace/${video%.*}-deinterlaced.mp4"
    touch -d "$year-$month-$day $hour:$minute:00" "$video"
fi
done

FIX DATES FILES in /2006/20060701/ structure

This touches files which have no exif info, to the date of the directory to that date with time 12:00

find . -type f -iname "*.mpg" | while read -r file; do     ymd=$(echo "$file" | grep -oE '[0-9]{8}' | head -1);     touch -t "${ymd}1200.00" "$file"; done

Touch date of current directory to date in path

for f in *; do
    [[ $f =~ ([0-9]{8}) ]] || continue
    touch -t "${BASH_REMATCH[1]}1200.00" "$f"
done

Broken thumb nail fix

# goto movie file in your library
# Add flags using below command
ffmpeg -i 73a1a221be4c.mp4 -c copy -video_track_timescale 90000 -movflags +faststart output.mp4
# replace movie file
cat output.mp4 > 73a1a221be4c.mp4 
rm output.mp4
# then fresh encoded videos in immich, then refresh metadata and thumbnails.

FIX Date unknown or incorrect dates

#!/bin/bash
if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <movie-file> <YYYYMMDD>"
    exit 1
fi

FILE="$1"
DATE="$2"

# Validate date format
if [[ ! "$DATE" =~ ^[0-9]{8}$ ]]; then
    echo "Error: date must be YYYYMMDD"
    exit 1
fi

# Convert YYYYMMDD -> YYYY:MM:DD 12:00:00 for EXIF metadata
YEAR="${DATE:0:4}"
MONTH="${DATE:4:2}"
DAY="${DATE:6:2}"
EXIFDATE="${YEAR}:${MONTH}:${DAY} 12:00:00"

# touch format: YYYYMMDD1200.00
TOUCHDATE="${DATE}1200.00"

echo "File:       $FILE"
echo "Date:       $DATE"
echo "Metadata:   $EXIFDATE"
echo "Filesystem: $TOUCHDATE"

# Change movie metadata dates
exiftool -overwrite_original \
    "-CreateDate=$EXIFDATE" \
    "-ModifyDate=$EXIFDATE" \
    "-TrackCreateDate=$EXIFDATE" \
    "-TrackModifyDate=$EXIFDATE" \
    "-MediaCreateDate=$EXIFDATE" \
    "-MediaModifyDate=$EXIFDATE" \
    "$FILE"

if [ $? -ne 0 ]; then
    echo "Error: exiftool failed"
    exit 1
fi

# Change filesystem timestamps
touch -t "$TOUCHDATE" "$FILE"

if [ $? -ne 0 ]; then
    echo "Error: touch failed"
    exit 1
fi

Complete config Immich/Powertools and Kiosk

For ML i’m using a second machine with GPU.

name: immich

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    # extends:
    #   file: hwaccel.transcoding.yml
    #   service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
    volumes:
      # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
      - ${UPLOAD_LOCATION}:/data
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - '2283:2283'
    depends_on:
      - redis
      - database
    restart: always
    healthcheck:
      disable: false

  immich-machine-learning:
    container_name: immich_machine_learning
    # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag.
    # Example tag: ${IMMICH_VERSION:-release}-cuda
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    # extends: # uncomment this section for hardware acceleration - see https://docs.immich.app/features/ml-hardware-acceleration
    #   file: hwaccel.ml.yml
    #   service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always
    healthcheck:
      disable: false

  immich-kiosk:
    image: ghcr.io/damongolding/immich-kiosk:latest
    container_name: immich-kiosk
    environment:
        LANG: "en_GB"
        TZ: "Europe/Amsterdam"
        # Required settings
        KIOSK_IMMICH_API_KEY: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx"
        KIOSK_IMMICH_URL: "https://mypix.henriaanstoot.nl/"
        # External url for image links/QR codes
        KIOSK_IMMICH_EXTERNAL_URL: ""
        # Clock
        KIOSK_SHOW_TIME: true
        KIOSK_TIME_FORMAT: 24
        KIOSK_SHOW_DATE: false
        KIOSK_DATE_FORMAT: YYYY/MM/DD
        KIOSK_CLOCK_SOURCE: client
        # Kiosk behaviour
        KIOSK_DURATION: 60
        KIOSK_DISABLE_SCREENSAVER: false
        KIOSK_OPTIMIZE_IMAGES: false
        KIOSK_USE_GPU: true
        KIOSK_BURN_IN_INTERVAL: 0
        KIOSK_BURN_IN_DURATION: 30
        KIOSK_BURN_IN_OPACITY: 70
        # Asset sources
        KIOSK_SHOW_ARCHIVED: false
        KIOSK_ALBUMS: ""
        KIOSK_ALBUM_ORDER: random
        KIOSK_EXCLUDED_ALBUMS: "ALBUM_ID,ALBUM_ID,ALBUM_ID"
        KIOSK_PEOPLE: ""
        KIOSK_REQUIRE_ALL_PEOPLE: false
        KIOSK_EXCLUDED_PEOPLE: "PERSON_ID,PERSON_ID,PERSON_ID"
        KIOSK_DATES: ""
        KIOSK_TAGS: ""
        KIOSK_EXCLUDED_TAGS: "TAG_VALUE,TAG_VALUE,TAG_VALUE"
        KIOSK_RATING: -1
        KIOSK_EXCLUDED_PARTNERS: "PARTNER_ID"
        KIOSK_MEMORIES: false
        KIOSK_BLACKLIST: "ASSET_ID,ASSET_ID,ASSET_ID"
        # FILTER
        KIOSK_FILTER_DATE: ""
        KIOSK_FILTER_NEWEST: 0
        KIOSK_FILTER_EXCLUDE_FACES: false
        KIOSK_FILTER_FAVORITES: false
        # UI
        KIOSK_SHOW_PROGRESS_BAR: false
        KIOSK_DISABLE_NAVIGATION: false
        KIOSK_DISABLE_UI: false
        KIOSK_FRAMELESS: false
        KIOSK_HIDE_CURSOR: false
        KIOSK_FONT_SIZE: 100
        KIOSK_BACKGROUND_BLUR: true
        KIOSK_BACKGROUND_BLUR_AMOUNT: 10
        KIOSK_THEME: fade
        KIOSK_LAYOUT: single
        KIOSK_SHOW_USER: false
        # Sleep mode
        # KIOSK_SLEEP_START: 22
        # KIOSK_SLEEP_END: 7
        # KIOSK_SLEEP_DIM_SCREEN: false
        # Transistion options
        KIOSK_TRANSITION: none
        KIOSK_FADE_TRANSITION_DURATION: 1
        KIOSK_CROSS_FADE_TRANSITION_DURATION: 1
        # Image display settings
        KIOSK_IMAGE_FIT: contain
        KIOSK_IMAGE_EFFECT: zoom
        KIOSK_IMAGE_EFFECT_AMOUNT: 120
        KIOSK_USE_ORIGINAL_IMAGE: false
        # Video
        KIOSK_SHOW_VIDEOS: true
        KIOSK_LIVE_PHOTOS: false
        KIOSK_LIVE_PHOTO_LOOP_DELAY: 0
        KIOSK_SHOW_ANIMATED_GIFS: false
        # Image metadata
        KIOSK_SHOW_IMAGE_RATING: true
        KIOSK_SHOW_OWNER: false
        KIOSK_SHOW_ALBUM_NAME: true
        KIOSK_SHOW_PERSON_NAME: false
        KIOSK_SHOW_PERSON_AGE: false
        KIOSK_SHOW_IMAGE_TIME: true
        KIOSK_IMAGE_TIME_FORMAT: 24
        KIOSK_SHOW_IMAGE_DATE: true
        KIOSK_IMAGE_DATE_FORMAT: YYYY-MM-DD
        KIOSK_SHOW_IMAGE_DESCRIPTION: true
        KIOSK_SHOW_IMAGE_CAMERA: false
        KIOSK_SHOW_IMAGE_EXIF: true
        KIOSK_SHOW_IMAGE_LOCATION: true
        KIOSK_HIDE_COUNTRIES: "HIDDEN_COUNTRY,HIDDEN_COUNTRY"
        KIOSK_SHOW_IMAGE_ID: false
        KIOSK_SHOW_IMAGE_QR: false
        KIOSK_SHOW_MORE_INFO: true
        KIOSK_SHOW_MORE_INFO_IMAGE_LINK: true
        KIOSK_SHOW_MORE_INFO_QR_CODE: true
        # More info actions
        KIOSK_LIKE_BUTTON_ACTION: favorite
        KIOSK_HIDE_BUTTON_ACTION: tag
        # Kiosk settings
        KIOSK_PORT: 3000
        KIOSK_BEHIND_PROXY: false
        KIOSK_DISABLE_URL_QUERIES: false
        KIOSK_DISABLE_CONFIG_ENDPOINT: false
        KIOSK_ENABLE_URL_BUILDER: false
        KIOSK_WATCH_CONFIG: false
        KIOSK_FETCHED_ASSETS_SIZE: 1000
        KIOSK_HTTP_TIMEOUT: 20
        KIOSK_PASSWORD: ""
        KIOSK_CACHE: true
        KIOSK_PREFETCH: true
        KIOSK_ASSET_WEIGHTING: true
    ports:
      - 3000:3000
    restart: always
    healthcheck:
      test: ["CMD", "/kiosk", "--healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s


  redis:
    container_name: immich_redis
    image: docker.io/valkey/valkey:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    healthcheck:
      test: redis-cli ping || exit 1
    restart: always

  database:
    container_name: immich_postgres
    image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
      # Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs
      # DB_STORAGE_TYPE: 'HDD'
    volumes:
      # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    shm_size: 128mb
    restart: always
  power-tools:
    container_name: immich_power_tools
    image: ghcr.io/immich-power-tools/immich-power-tools:latest
    volumes:
      - immich-power-tools-data:/app/data
    ports:
      - "8001:3000"
    env_file:
      - .env
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

volumes:
  model-cache:
  immich-power-tools-data:

Immich Workflow Filters

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)$

Immich hints and tips

A dump of my immich experience

Getting lists of filenames from an album.

Create an API key from your Immich instance.

NOTE: You will need album.read and asset.read

Then get an ID from an album to get images from.
Open een album in your browser and copy the ID from the URL

Code to get a filelist using Curl
NOTE: Not working on 3.x

curl -s -H "x-api-key: 2Nk4sO4eEm001Cm1Dsnl32UVDEvxxxxxxxxxxxxxxxxx" "https://myphotos.example.com/api/albums/f6a300c2-5027-4c38-a367-xxxxxxxxxxxxxx" | jq -r '.
assets[].originalFileName'

Fixing WhatsApp

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.

git clone https://github.com/FlorianKrauseResearch/Immich-Metadata-Update.git
(somewhere on your desktop system/laptop)

Look at installation and usage here: https://github.com/FlorianKrauseResearch/Immich-Metadata-Update
Create a new API key with enough rights!

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

I’ve edited immich_metadata_update/filters.py

BUILTIN_PATTERNS: dict[str, DatePattern] = {
    "whatsapp": DatePattern(
        name="WhatsApp",
        regex=r"^IMG-(\d{8})-WA\d{4}\.\w+$",
        date_format="%Y%m%d",
    ),
    "whatsappvid": DatePattern(
        name="WhatsApp",
        regex=r"^VID-(\d{8})-WA\d{4}\.\w+$",
        date_format="%Y%m%d",
    ),
    "screenshot_basic": DatePattern(
        name="Screenshot (basic)",
        regex=r"^Screenshot_(\d{8})-\d{6}\.\w+$",
        date_format="%Y%m%d",
    ),
    "screenshot_full": DatePattern(
        name="Screenshot (with app name)",
        regex=r"^Screenshot_(\d{4}-\d{2}-\d{2}).*$",
        date_format="%Y-%m-%d",
    ),
    "signal": DatePattern(
        name="Signal",
        regex=r"^signal-(\d{4}-\d{2}-\d{2})-\d{2}-\d{2}-\d{2}-.*$",
        date_format="%Y-%m-%d",
    ),
}
python3 run.py --preset whatsappvid
python3 run.py --preset whatsappvid --apply corrections.json

Incorrect MAP location (0,0 problem, AKA Null Island)

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.

Another solution is moving them to Archive!

Uploading using immich-go

https://github.com/simulot/immich-go

./immich-go upload from-folder --server http://192.168.1.2:2283 --api-key GdMq6lZU8Szw6Lc2TXXXXXXXXXXXXXXXXXXXXXX  --folder-as-album=FOLDER ~/Pictures/Screenshots/

NOTE: Subdirs become new albums.

Immich Power Tools

https://github.com/immich-power-tools/immich-power-tools

  • 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()

Immich and (not) Google Timeline

Google killed timeline.

I’ve been experimenting in the past with GPS (gpx) mappers and alternatives.

Now I’ve installed Dawarich, which can use the photos in my immich library.

https://dawarich.app

Spin up a docker instance, create an API key in immich, and GO.

I’ve imported google’s timeline.json from my phone.

Auto post locations to your instance API using:

Software i use(d)

My much used set of tools (old draft I edited)

File managers

  • Midnight Commander
  • Thunar
  • Nautilus
  • nnn
  • Dolphin

Generic tools

  • rsync
  • dcfldd – an enhanced version of dd
  • screen/tmux
  • fdupes
  • ghex / xxd

Connecting/networking tools

  • mosh – roaming, faster, using ssh
  • sshfs – mount remote filesystems over ssh (see ssh tricks)
  • wavemon – wifi info
  • GNS3 – Graphical Network Simulator-3
  • Wireshark
  • PHPIpam
  • Homelable

Encryption

  • Luks
  • ecryptfs

Graphical/Photo

  • Gimp
  • Eog
  • rawtherapee
  • Inkscape
  • Darktable
  • PureRef
  • digiKam
  • Krita
  • Hugin
  • ImageMagick

Documents/Notekeeping

  • Vim
  • Joplin
  • Paperless-ngx
  • DrawIO – For network drawings
  • LibreOffice/OnlyOffice -> EuroOffice
  • Scribus

3D Print/Lasercutting

  • Cura
  • Orcaslicer
  • Bambuddy
  • LightBurn
  • Model editors: Blender and OpenScad, Meshroom, Sketchup

Coding

  • Mostly CLI
  • PlatformIO
  • Arduino IDE
  • VSCodium (stripped Visual Studio)

Electronics

  • Fritzing
  • Kicad
  • Logic

Ebook / Comic readers

  • Calibre
  • FBReader

GFX/Video

  • Blender
  • kdenlive
  • OBS Studio
  • Shotwell
  • Handbrake
  • ffmpeg
  • VLC
  • Kodi/Libreelec

Music

  • CLI abc tools
  • Musescore
  • Bagpipe Music Player
  • Audacity (after removing all bad things be-ing added in 2021)
  • LMMS
  • Ardour

Virtualisation/Emulation

  • proxmox
  • libvirt
  • ovirt
  • guestfish
  • dosbox
  • pcem
  • Martypc

Password management

  • Keepass / KeepassXC

API

  • Postman
  • Flask (Python) see ledserver

Beamer

  • Mapmap
  • LPMT
  • Qprompt

Brewing

  • Brouwhulp
  • Brewfather (web)

Web/App alternatives for bad companies (Mostly own hosted)

  • Gmail – Hosted elsewhere – Thunderbird + web
  • Google Drive – Nextcloud
  • Spotify – Navidrome
  • Google Photos (I never used this, i used Gallery2/3/Wipigo ) – Immich
  • Youtube – Jellyfin for own movies
  • Whatsapp/Google Chat – My own mattermost server, signal and IRC
  • Teams – Ownhosted jitsi
  • Teamviewer – Rustdesk
  • Google Timeline – Dawarich

Server generic

  • Databases : mariadb/mongodb/influxdb/sqlite
  • Grafana
  • NodeRed
  • gitea
  • HomeAssistant
  • Bookstack
  • Librenms
  • Check_mk (i’ve started with Netsaint (1999), Nagios,Icinga,
  • Mosquitto

Unsorted stuff

  • vimperator (old)
  • links/curl/dsniff/urlsnarf
  • Databases (mariadb/mongodb/influxdb/sqlite)
  • Metabase
  • Adminder
  • Scrot (snapshot tool)
  • Luminace-hdr
  • GDlib
  • Dia (old)
  • Blackmagic Fusion
  • gcalcli
  • Gcalcron
  • Domoticz
  • MQTT-Explorer
  • Android studio
  • Nextion IDE
  • Irssi
  • Mutt
  • Mailcow
  • Tinymediamanager
  • Cacti (old)
  • Winbox (Mikrotik)
  • iptraf
  • ntopng
  • Twiki/Foswiki (old)
  • Digikam
  • My own photo manager
  • Cewe Photobooks
  • qdlsrdashboard
  • gphoto2
  • Ktechlab
  • Ardour5
  • Puredata
  • Cadence
  • Natron
  • yt-downloader
  • 4k downloader
  • Taggers
  • Mp3tag
  • Tinymediamanager
  • MusicBrainz Picard
  • Beets
  • Music players
  • MOC
  • SoftSqueeze
  • Clementine
  • My Badly Designed Sound Machine
  • Server stuff
  • Namazu2
  • Netdata
  • Ntopng
  • Snort/Snortsam
  • Virtualisation

Window managers (See other post)

  • Xmonad (current)
  • Gnome (current)
  • Enlightenment (old)
  • Compiz (old)
  • Fluxbox (old)
  • Ratpoison (tried)
  • Twm (tried)
  • Xfce (old)
  • Window Maker(old)
  • Sawfish (old)
  • Kde (old)
  • i3 (old)
  • IceWM (old)
  • Motif (old)
  • Flwm (old)
  • Fvwm (old)
  • Ximian desktop (bought) (old)

  • Home Drawing
    • Sweethome3D
    • Blender
    • Sketchup
    • Drawio
  • Old but cool
    • Mainactor
    • Appleshake
    • Zbruch
    • Povray
    • Lightzone

Other tools:

Git, tig, xdotool, nmon, ntop, iotop, etc etc (lijstje genereren)

Immich is amazing

I’m running this Google Photos alternative for a week now, and I am pleasantly suprised.

UPDATE: 20260817 …. 220000 Images now

UPDATE: Installed power-tools, python scripts to manipulate dates and immich-go

  • Face detection : spot on
  • Responsiveness : fast!, even with a large library
  • Android uploads : it just works! (I used Nextcloud before)
  • Movies : plays smoothly (there is a cast button for movies and images)

The face detection had only 1 mismatch in my library.

Negatives?

Well maybe album management, it could be better or more flexible

Some search tests:

  • Food – indeed found food
  • Rum – found drinks
    (I changed search query to OCR, and it gave me images with the word RUM on it) !
  • Dog – First ones are dogs indeed, after that other animals
  • Smiling / Kissing works
  • Hair/red/computer/music/comic

Amazing results!

Features (some)

  • Docker instance for simple upgrades
  • Facial Recognition
  • Hardware Transcoding
  • Hardware-Accelerated Machine Learning
  • Reverse Geocoding (see below)

Lets copy the rest of my photo libary to this server.
(Storage is on a 10Gbit fiberoptic iSCSI device)

Tiny animator for stop-motion

I was working on a RP2040 HID project, but I needed some components I didn’t have … right now .. again ..

So I made something else ..

A tiny animator for stop motion animations using my webcam, python and OpenCV.

For claymotion or lego or whatever.

The program displays your webcam with the previous snapshot overlayed, so you can position everything relative to your previous snapshot.

Difference between two shots.

Press B to take a frame.

Just a proof of concept using a (BAD) webcam. (Don’t look at my hand )

CODE (short but you need OpenCV)

import  cv2
from datetime import datetime
# black is just a start empty image .. 
img=cv2.imread("black.png");
cap = cv2.VideoCapture(0)

while True: 

    ret,vid=cap.read()
    dim = (800,600)
    img1 = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)
    vid1 = cv2.resize(vid, dim, interpolation = cv2.INTER_AREA)

    result=cv2.addWeighted(img1,0.5,vid1,0.5,0)
    cv2.imshow('overlay', result)
    if(cv2.waitKey(10) & 0xFF == ord('b')):
            now = datetime.now()
            current_time = now.strftime("%d_%m_%Y_%H_%M_%S")
            filename = '%s.png' % current_time
            if not cv2.imwrite(filename, vid1):
                raise Exception("Could not write image")
            img=cv2.imread(filename);

Pressing B fills your directory with PNG’s
like 24_10_2023_00_01_01.png (date formatted)

convert to GIF

convert -delay 10 -loop 0 24*.png animation.gif

Triple screen panorama viewer

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