Files
camera-gallery/scripts/shutterlinksort-usr-bin

297 lines
9.5 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ======================================================================
# shutterlinksort
# ----------------
# Camera image downloader and organizer for PTP/IP-connected devices.
#
# Connects to a camera over network (using gphoto2), downloads new
# photos, extracts EXIF metadata (date, shutter count), and sorts
# images into folders by date.
#
# Usage:
# shutterlinksort
# Dependencies:
# gphoto2, exiftool, bash
#
# Author: James Blackmore (reclusejay)
# Version: 1.0
# ======================================================================
# --- Safety options ---
# -e : Exit immediately if a command exits with non-zero status.
# -u : Treat unset variables as an error and exit.
# -o pipefail : Fail a pipeline if *any* command in it fails.
set -euo pipefail
# ============================================================
# Cleanup handler — runs on interrupt/termination
# ============================================================
cleanup() {
# Disable any special globbing options we enabled
shopt -u nullglob nocaseglob || true
# Delete any 0-byte (incomplete) downloads if present
find "$cam_dl_dir" -type f -size 0 -delete 2>/dev/null || true
log "Caught interrupt, exiting"
exit 0
}
# Trap cleanup on Ctrl+C, kill, or quit signals
trap cleanup SIGINT SIGTERM SIGQUIT
# ============================================================
# Function: php_get
# Pulls a config value from the PHP $CONFIG array
# ============================================================
php_get() {
if ! command -v php >/dev/null 2>&1; then
# fallback to php-cli
if command -v php-cli >/dev/null 2>&1; then
php-cli -r "include('../src/includes/config.php'); print \$CONFIG['$1'];"
else
echo "Error: Neither 'php' nor 'php-cli' is available."
exit 1
fi
else
php -r "include('../src/includes/config.php'); print \$CONFIG['$1'];"
fi
return 0
}
# ============================================================
# Load all settings from PHP config
# ============================================================
cam_uuid=$(php_get "camera_uuid")
cam_dl_dir=$(php_get "upload_dir")
cam_sort_dir=$(php_get "sorted_img_dir")
cam_index_file=$(php_get "index_file")
cam_xml_url=$(php_get "camera_xml_url")
cam_ext_format=$(php_get "camera_ext_formats")
cam_mac=$(php_get "camera_mac")
g2_port=$(php_get "gphoto2_config_port")
g2_model=$(php_get "gphoto2_config_model")
g2_guid=$(php_get "gphoto2_config_guid")
g2_conf=$(php_get "gphoto2_config_file")
# Create the index file if missing
[ -f "$cam_index_file" ] || echo 0 > "$cam_index_file"
# ============================================================
# Global variables
# ============================================================
debug=true
ver="v.25.10.1"
STATUS="INITIALIZING"
CAM_IP=""
# ============================================================
# Function: log
# Provides timestamped console and syslog logging
# ============================================================
log() {
local msg lev full_msg
if $debug; then
msg="${1:-NO MESSAGE!}"
lev="${2:-info}"
full_msg="[debug $lev] Version: $ver - $(date +[%Y/%m/%d-%H:%M:%S]) - $msg"
# Always print to stdout for debugging
echo "$full_msg"
# Try syslog first; fallback to a file log if logger fails
logger -t camera-download-sort "$full_msg" 2>/dev/null || \
echo "$full_msg" >> /var/log/camera-download-sort.log
fi
}
# ============================================================
# Function: find_ip_by_mac
# Locates the IP address of a device by its MAC address
# ============================================================
find_ip_by_mac() {
local mac ip file
# Normalize MAC to lowercase for comparison
mac=$(echo "$1" | tr '[:upper:]' '[:lower:]')
[ -z "$mac" ] && log "No MAC provided" && return 1
# 1⃣ Primary source: /proc/net/arp (most reliable)
ip=$(awk -v mac="$mac" 'NR>1 && tolower($4)==mac {print $1; exit}' /proc/net/arp)
# 2⃣ Fallback: BusyBox arp command output
if [ -z "$ip" ] && command -v arp >/dev/null 2>&1; then
ip=$(arp | awk -v mac="$mac" 'NR>1 && tolower($4)==mac {print $1; exit}')
fi
# 3⃣ Fallback: dnsmasq lease files
if [ -z "$ip" ]; then
for file in /tmp/dhcp.leases /var/lib/misc/dnsmasq.leases; do
[ -f "$file" ] || continue
ip=$(awk -v mac="$mac" 'tolower($2)==mac {print $3; exit}' "$file")
[ -n "$ip" ] && break
done
fi
# If found, set global CAM_IP variable
if [ -n "$ip" ]; then
CAM_IP="$ip"
log "Camera found at $CAM_IP"
return 0
fi
# Not found
return 1
}
# ============================================================
# Function: is_camera_connected
# Checks whether the cameras MAC address is visible
# ============================================================
is_camera_connected() {
if find_ip_by_mac "$cam_mac"; then
if [ "$STATUS" = "WAITING" ]; then
# Already in waiting state — skip
return 1
fi
STATUS="CONNECTED"
return 0
fi
STATUS="DISCONNECTED"
return 1
}
# ============================================================
# Function: download_images
# Uses gphoto2 (PTP/IP) to pull new images from camera
# ============================================================
download_images() {
local wd="$PWD"
STATUS="WAITING"
cd "$cam_dl_dir" || return 1
# Create gphoto2 config file if it doesnt exist
if [ ! -f "$g2_conf" ]; then
{
echo "$g2_port$CAM_IP"
echo "$g2_model"
echo "$g2_guid$cam_uuid"
} > "$g2_conf"
fi
# Check for gphoto2 availability
if command -v gphoto2 >/dev/null 2>&1; then
# Download all new files, skipping ones that already exist
if gphoto2 --port "ptpip:$CAM_IP" -P --skip-existing; then
log "Downloaded new images from $CAM_IP"
cd "$wd" || true
return 0
fi
log "gphoto2 download failed" error
else
log "gphoto2 not found — skipping download" warn
fi
cd "$wd" || true
return 1
}
# ============================================================
# Function: sort_images
# Sorts downloaded images into folders by EXIF date
# ============================================================
sort_images() {
local ext img datetime shuttercount yyyy mm dd timepart hhmmss ext_lower \
output_dir new_filename new_file uid index
STATUS="SORTING"
log "$STATUS"
# Ensure exiftool exists
command -v exiftool >/dev/null 2>&1 || {
log "exiftool missing — cannot sort" error
return 1
}
# Get current image index number
index=$(cat "$cam_index_file")
# Loop over each configured image extension (e.g. JPG ARW)
for ext in $cam_ext_format; do
# Enable flexible globbing (case-insensitive, no literal fail)
shopt -s nullglob nocaseglob
# Iterate through each matching file
for img in "$cam_dl_dir"/*."$ext"; do
# Pull EXIF date and shutter count
readarray -t exif_data < <(exiftool -s -s -s -DateTimeOriginal -ShutterCount "$img")
datetime="${exif_data[0]:-0000:00:00 00:00:00}"
shuttercount="${exif_data[1]:-}"
log "Processing $img (index $index)"
# Generate a unique ID if no shuttercount found
if [ -z "$shuttercount" ]; then
uid="rn${RANDOM}idx${index}"
log "No shuttercount in $img, uid=$uid"
else
uid="sc${shuttercount}idx${index}"
log "Shuttercount=$shuttercount uid=$uid"
fi
# Parse EXIF date into components
yyyy=$(echo "$datetime" | cut -d':' -f1)
mm=$(echo "$datetime" | cut -d':' -f2)
dd=$(echo "$datetime" | cut -d':' -f3 | cut -d' ' -f1)
timepart=$(echo "$datetime" | cut -d' ' -f2)
hhmmss=$(echo "$timepart" | tr -d ':')
# Normalize extension to lowercase
ext_lower=${ext,,}
# Build destination folder structure
output_dir="$cam_sort_dir/$ext_lower/$yyyy/$mm/$dd"
mkdir -p "$output_dir" || { log "mkdir failed: $output_dir" error; return 1; }
# Construct new filename
new_filename="${hhmmss}_${uid}.${ext_lower}"
new_file="$output_dir/$new_filename"
log "Moving $img -> $new_file"
# Move and increment index
if mv "$img" "$new_file"; then
index=$((index + 1))
else
log "Failed to move $img" error
fi
done
# Update index file after processing each extension type
printf '%s\n' "$index" > "$cam_index_file"
done
STATUS="DONE"
log "$STATUS"
return 0
}
# ============================================================
# Main loop — runs forever
# ============================================================
log "Camera script started"
while :; do
# If camera detected and not already downloading/sorting
if is_camera_connected && [ "$STATUS" != "WAITING" ] && [ "$STATUS" != "SORTING" ]; then
download_images || log "Download step failed" error
# If camera disconnected, begin sorting
elif [ "$STATUS" = "DISCONNECTED" ]; then
sort_images || log "Sort step failed" error
fi
# Avoid hammering system; check every 2 seconds
sleep 2
done