Compare commits
9 Commits
c3fd4d9f57
...
dev
| Author | SHA256 | Date | |
|---|---|---|---|
| 01517ca4c7 | |||
| 118cdc0dbf | |||
| 370f32ac06 | |||
| 58554db579 | |||
| 02fb5a029d | |||
| f23d7d4790 | |||
| 756103e25f | |||
| 53f72f2c37 | |||
| 821e20fd76 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ src/img/**
|
||||
src/pages/infophp.php
|
||||
src/pages/gethash.php
|
||||
vars.php
|
||||
**.github
|
||||
|
||||
221
install
Normal file
221
install
Normal file
@@ -0,0 +1,221 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# --- Setup ---
|
||||
BASEDIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SRCDIR="$BASEDIR/.src"
|
||||
SCRIPTDIR="$SRCDIR/scripts"
|
||||
BACKUPDIR="/tmp/shutterlinksort-backup"
|
||||
|
||||
# --- Functions ---
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: sudo $0 [OPTIONS] [WEBROOT]
|
||||
|
||||
Options:
|
||||
--help Show this help message
|
||||
--dry-run Preview actions without changing any files
|
||||
--rollback Restore the last backup
|
||||
--uninstall Remove installed files and web content
|
||||
|
||||
Arguments:
|
||||
WEBROOT Required: destination for web content
|
||||
|
||||
Examples:
|
||||
Install normally (default web root):
|
||||
sudo $0
|
||||
|
||||
Install to custom web root:
|
||||
sudo $0 /srv/http/shutterlinksort
|
||||
|
||||
Dry-run install (preview actions):
|
||||
./install.sh --dry-run /srv/http/shutterlinksort
|
||||
|
||||
Rollback from last backup:
|
||||
sudo $0 --rollback
|
||||
|
||||
Uninstall installed files:
|
||||
sudo $0 --uninstall
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
backup_if_exists() {
|
||||
local target="$1"
|
||||
if [ -e "$target" ]; then
|
||||
local target_dir
|
||||
target_dir="$(dirname "$target")"
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo " [DRY-RUN] Would back up existing $(basename "$target") to $BACKUPDIR/$target_dir"
|
||||
return
|
||||
fi
|
||||
mkdir -p "$BACKUPDIR/$target_dir"
|
||||
cp -a "$target" "$BACKUPDIR/$target_dir/"
|
||||
echo " • Backed up existing $(basename "$target")"
|
||||
fi
|
||||
}
|
||||
|
||||
rollback() {
|
||||
if [ ! -d "$BACKUPDIR" ]; then
|
||||
echo "No backup found at $BACKUPDIR. Nothing to restore."
|
||||
exit 1
|
||||
fi
|
||||
read -pr "Are you sure you want to rollback all files from backup? [y/N]: " confirm
|
||||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||
echo "=== Rolling back from backup ==="
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo " [DRY-RUN] Would restore backup from $BACKUPDIR to /"
|
||||
else
|
||||
cp -a "$BACKUPDIR/"* /
|
||||
echo "Rollback complete ✅"
|
||||
fi
|
||||
else
|
||||
echo "Rollback cancelled."
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
read -pr "Are you sure you want to uninstall all shutterlinksort files and web content? [y/N]: " confirm
|
||||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||
echo "=== Uninstalling shutterlinksort ==="
|
||||
for srcfile in "$SCRIPTDIR"/shutterlinksort-*; do
|
||||
filename="$(basename "$srcfile")"
|
||||
progname="${filename%%-*}"
|
||||
pathpart="${filename#*-}"
|
||||
targetpath="${pathpart//-//}"
|
||||
target="/$targetpath/$progname"
|
||||
if [ "$DRYRUN" = true ] && [ -e "$target" ]; then
|
||||
echo " [DRY-RUN] Would remove $target"
|
||||
continue
|
||||
fi
|
||||
if [ -e "$target" ]; then
|
||||
rm -f "$target"
|
||||
echo " • Removed $target"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "$WEBROOT" ]; then
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo " [DRY-RUN] Would remove web content at $WEBROOT"
|
||||
else
|
||||
rm -rf "$WEBROOT"
|
||||
echo " • Removed web content at $WEBROOT"
|
||||
fi
|
||||
fi
|
||||
echo "Uninstall complete ✅"
|
||||
else
|
||||
echo "Uninstall cancelled."
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Parse arguments ---
|
||||
DRYRUN=false
|
||||
ACTION="install"
|
||||
|
||||
# Show usage if no arguments were provided
|
||||
if [ $# -eq 0 ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--help)
|
||||
usage
|
||||
;;
|
||||
--dry-run)
|
||||
DRYRUN=true
|
||||
shift
|
||||
;;
|
||||
--rollback|--uninstall)
|
||||
ACTION="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
WEBROOT="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Main ---
|
||||
case "$ACTION" in
|
||||
--rollback)
|
||||
rollback
|
||||
;;
|
||||
--uninstall)
|
||||
uninstall
|
||||
;;
|
||||
install)
|
||||
echo "=== Installing shutterlinksort components ==="
|
||||
echo "Using web root: $WEBROOT"
|
||||
echo "Backup directory: $BACKUPDIR"
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo "[DRY-RUN mode: no files will be changed]"
|
||||
fi
|
||||
echo
|
||||
|
||||
# Clear previous backup
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo "[DRY-RUN] Would remove existing $BACKUPDIR"
|
||||
else
|
||||
rm -rf "$BACKUPDIR"
|
||||
fi
|
||||
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo "[DRY-RUN] Would create $BACKUPDIR"
|
||||
else
|
||||
mkdir -p "$BACKUPDIR"
|
||||
fi
|
||||
|
||||
# Install scripts
|
||||
for srcfile in "$SCRIPTDIR"/shutterlinksort-*; do
|
||||
[ -e "$srcfile" ] || continue
|
||||
filename="$(basename "$srcfile")"
|
||||
progname="${filename%%-*}"
|
||||
pathpart="${filename#*-}"
|
||||
targetpath="${pathpart//-//}"
|
||||
target="/$targetpath/$progname"
|
||||
|
||||
echo "→ Installing $progname → $target"
|
||||
mkdir -p "/$targetpath" 2>/dev/null || true
|
||||
backup_if_exists "$target"
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo " [DRY-RUN] Would install $srcfile → $target"
|
||||
else
|
||||
install -m 755 "$srcfile" "$target"
|
||||
fi
|
||||
done
|
||||
|
||||
# Install web content
|
||||
echo
|
||||
echo "=== Installing web content ==="
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo " [DRY-RUN] Would create $WEBROOT"
|
||||
else
|
||||
mkdir -p "$WEBROOT"
|
||||
fi
|
||||
|
||||
if [ -d "$WEBROOT" ]; then
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo " [DRY-RUN] Would back up existing web content to $BACKUPDIR/$(basename "$WEBROOT")"
|
||||
else
|
||||
cp -a "$WEBROOT" "$BACKUPDIR/"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DRYRUN" = true ]; then
|
||||
echo " [DRY-RUN] Would copy $SRCDIR/web/ → $WEBROOT"
|
||||
else
|
||||
cp -r "$SRCDIR/web/"* "$WEBROOT/"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Web content installed to: $WEBROOT"
|
||||
echo "All backups stored in: $BACKUPDIR"
|
||||
echo "Installation complete ✅"
|
||||
echo "To rollback, run: sudo $0 --rollback"
|
||||
echo "To uninstall, run: sudo $0 --uninstall"
|
||||
;;
|
||||
esac
|
||||
67
scripts/shutterlinksort-etc-init.d
Normal file
67
scripts/shutterlinksort-etc-init.d
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash /etc/rc.common
|
||||
# shutterlinksort.init
|
||||
# Copyright (C) 2025 reclusejay (James Blackmore)
|
||||
# OpenWrt init script for shutterlinksort
|
||||
|
||||
# shellcheck disable=SC2034 # START/STOP used in rc.common
|
||||
# START/STOP handled by /etc/rc.common
|
||||
START=96
|
||||
STOP=10
|
||||
|
||||
SCRIPT_NAME="shutterlinksort"
|
||||
LOCK_FILE="/var/lock/$SCRIPT_NAME"
|
||||
|
||||
start() {
|
||||
if [ -f "$LOCK_FILE" ]; then
|
||||
echo "$SCRIPT_NAME is already running (pid=$(cat "$LOCK_FILE"))"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Starting $SCRIPT_NAME..."
|
||||
"$SCRIPT_NAME" >/dev/null 2>&1 &
|
||||
pid=$!
|
||||
|
||||
# Verify the process started successfully
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo "$pid" > "$LOCK_FILE"
|
||||
echo "$SCRIPT_NAME started with pid=$pid"
|
||||
else
|
||||
echo "Failed to start $SCRIPT_NAME"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [ ! -f "$LOCK_FILE" ]; then
|
||||
echo "$SCRIPT_NAME not running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
pid=$(cat "$LOCK_FILE")
|
||||
echo "Stopping $SCRIPT_NAME (pid=$pid)..."
|
||||
|
||||
if kill "$pid" 2>/dev/null; then
|
||||
sleep 1
|
||||
rm -f "$LOCK_FILE"
|
||||
echo "$SCRIPT_NAME stopped"
|
||||
else
|
||||
echo "Failed to stop $SCRIPT_NAME (process may not exist)"
|
||||
rm -f "$LOCK_FILE"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
restart() {
|
||||
echo "Restarting $SCRIPT_NAME..."
|
||||
stop
|
||||
sleep 2
|
||||
start
|
||||
}
|
||||
|
||||
boot() {
|
||||
start
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
stop
|
||||
}
|
||||
296
scripts/shutterlinksort-usr-bin
Normal file
296
scripts/shutterlinksort-usr-bin
Normal file
@@ -0,0 +1,296 @@
|
||||
#!/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 camera’s 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 doesn’t 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
|
||||
@@ -16,6 +16,13 @@ $cache_file = $CONFIG['cache_file'];
|
||||
// Serve cached gallery JSON if fresh
|
||||
if (file_exists($cache_file)) {
|
||||
$data = json_decode(file_get_contents($cache_file), true);
|
||||
$count = count_all_images($CONFIG);
|
||||
if ($count !== $data['countall']) {
|
||||
unlink($cache_file);
|
||||
$data = buildGalleryCache($CONFIG);
|
||||
if (!is_dir(dirname($cache_file))) @mkdir(dirname($cache_file), 0775, true);
|
||||
file_put_contents($cache_file, json_encode($data, JSON_PRETTY_PRINT));
|
||||
}
|
||||
} else {
|
||||
$data = buildGalleryCache($CONFIG);
|
||||
if (!is_dir(dirname($cache_file))) @mkdir(dirname($cache_file), 0775, true);
|
||||
@@ -37,6 +44,53 @@ echo json_encode([
|
||||
|
||||
// --- FUNCTIONS ---
|
||||
|
||||
function count_all_images($CONFIG) {
|
||||
$dir = $CONFIG['sorted_img_dir'];
|
||||
if (!is_dir($dir)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile()) {
|
||||
$ext = strtolower($file->getExtension());
|
||||
if ($ext === 'arw' || $ext === 'raw' ||
|
||||
$ext === 'jpg' || $ext === 'png' ) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
function count_raw_files($CONFIG) {
|
||||
$dir = $CONFIG['sorted_img_dir'].'/arw';
|
||||
if (!is_dir($dir)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile()) {
|
||||
$ext = strtolower($file->getExtension());
|
||||
if ($ext === 'arw' || $ext === 'raw') {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
function buildGalleryCache($CONFIG) {
|
||||
$dir = $CONFIG['gallery_dir'];
|
||||
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
|
||||
@@ -74,11 +128,13 @@ function buildGalleryCache($CONFIG) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
'generated' => date('c'),
|
||||
'count' => count($images),
|
||||
'countjpg' => count($images),
|
||||
'countraw' => count_raw_files($CONFIG),
|
||||
'countall' => count_all_images($CONFIG),
|
||||
'images' => $images
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,13 +25,21 @@ $CONFIG = [
|
||||
'blacklist_commands' => ['rm', 'shutdown', 'reboot', 'passwd', 'dd', ':(){'],
|
||||
'default_dir' => '/home',
|
||||
|
||||
|
||||
// === ShutterLinkSort bash file config ===
|
||||
'camera_mac' => '00:00:00:00:00:00',
|
||||
'camera_uuid' => 'e4:ec:89:a3:0c:b5:4d:1d:cb:e3:a8:e1:ff:68:48:ed',
|
||||
'camera_xml_url' => 'http://ip:1900/DeviceDescription.xml',
|
||||
'camera_ext_formats' => 'ARW JPG',
|
||||
'gphoto2_config_file' => '/root/.gphoto/settings',
|
||||
'gphoto2_config_port' => 'gphoto2=port=ptpip:',
|
||||
'gphoto2_config_model' => 'gphoto2=model=PTP/IP Camera',
|
||||
'gphoto2_config_guid' => 'ptp2_ip=guid=',
|
||||
|
||||
|
||||
];
|
||||
|
||||
// === JavaScript Variables ===
|
||||
$CONFIG += [
|
||||
$CONFIG_JS = [
|
||||
// === Index System Wide ===
|
||||
'index_admin_status_update_interval' => 50000,
|
||||
|
||||
@@ -52,6 +60,7 @@ $CONFIG += [
|
||||
$CONFIG += [
|
||||
// === Paths ===
|
||||
'base_dir' => $ROOT_DIR, // Location of root directory for the web server usually (Define at the top $ROOT_DIR)
|
||||
'sorted_img_dir' => $ROOT_DIR.'/img/sorted',
|
||||
'gallery_dir' => $ROOT_DIR.'/img/sorted/jpg', // Location of the images
|
||||
'upload_dir' => $ROOT_DIR.'/img/uploads', // Location for uploaded images
|
||||
'index_file' => $ROOT_DIR.'/img/.index', // Location of the index file for image naming
|
||||
|
||||
@@ -45,14 +45,17 @@ $disk0_free = disk_free_space('/home');
|
||||
$disk0_used = $disk0_total - $disk0_free;
|
||||
$disk0_usage = round(($disk0_used / $disk0_total) * 100, 1);
|
||||
|
||||
// --- Gallery image count ---
|
||||
// --- Image count from cache/gallery.json ---
|
||||
// --- Gallery image countjpg ---
|
||||
// --- Image countjpg from cache/gallery.json ---
|
||||
$jsonFile = __DIR__ . '/../cache/gallery.json';
|
||||
if (file_exists($jsonFile)) {
|
||||
$gallery = json_decode(file_get_contents($jsonFile), true);
|
||||
$count = $gallery['count'] ?? 0;
|
||||
$countjpg = $gallery['countjpg'] ?? 0;
|
||||
$countraw = $gallery['countraw'] ?? 0;
|
||||
} else {
|
||||
$count = 0;
|
||||
// generate the file
|
||||
$countjpg = 0;
|
||||
$countraw = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,5 +73,6 @@ echo json_encode([
|
||||
'disk0' => $disk0_usage,
|
||||
'disk0_total' => $disk0_total,
|
||||
'disk0_used' => $disk0_used,
|
||||
'images' => $count,
|
||||
'imagesjpg' => $countjpg,
|
||||
'imagesraw' => $countraw,
|
||||
]);
|
||||
|
||||
@@ -53,15 +53,8 @@ unset($nav_order,$nav_admin,$nav_hidden);
|
||||
<title><?= htmlspecialchars($CONFIG['site_name']) ?></title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<script>
|
||||
const CONFIG = {
|
||||
options_status_interval: <?= ($CONFIG['options_status_interval'] ?? 30000) ?>,
|
||||
options_status_services: <?= ($CONFIG['options_status_services'] ?? []) ?>,
|
||||
shell_print_howto_to_logconsole: <?= ($CONFIG['shell_print_howto_to_logconsole'] ?? false) ?>,
|
||||
home_chart_color_threshold_high: <?= ($CONFIG['home_chart_color_threshold_high'] ?? 70) ?>,
|
||||
home_chart_color_threshold_medium: <?= ($CONFIG['home_chart_color_threshold_medium'] ?? 50) ?>,
|
||||
index_admin_status_update_interval: <?= ($CONFIG['index_admin_status_update_interval'] ?? 50) ?>,
|
||||
};
|
||||
</script>
|
||||
const CONFIG = <?= json_encode($CONFIG_JS, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) ?>;
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
document.addEventListener('page-loaded', e => {
|
||||
if (e.detail.page !== "home") return;
|
||||
|
||||
const stats = ['Cpu','Ram','Zram','Swap','Disk','Disk0','Images'];
|
||||
const stats = ['Cpu','Ram','Zram','Swap','Disk','Disk0','Imagesjpg','Imagesraw'];
|
||||
const elements = {};
|
||||
const bars = {};
|
||||
const prev = {};
|
||||
@@ -32,7 +32,7 @@ document.addEventListener('page-loaded', e => {
|
||||
const numVal = Number(val) || 0;
|
||||
|
||||
// Animate number
|
||||
if (key === 'Images') {
|
||||
if (key === 'Imagesjpg' || key === 'Imagesraw') {
|
||||
// Raw number
|
||||
animateValue(elements[key], prev[key], numVal, 400, false);
|
||||
} else {
|
||||
@@ -55,7 +55,7 @@ document.addEventListener('page-loaded', e => {
|
||||
|
||||
// Progress bar
|
||||
if (bars[key]) {
|
||||
let pct = key === 'Images' ? Math.min(numVal / 100 * 100, 100) : numVal;
|
||||
let pct = key === 'Imagesjpg' || key === 'Imagesraw' ? Math.min(numVal / 100 * 100, 100) : numVal;
|
||||
bars[key].style.width = pct + '%';
|
||||
}
|
||||
|
||||
|
||||
@@ -38,14 +38,14 @@
|
||||
<div class="progress-bar"><div id="barDisk0"></div></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>Images 🖼️</h3>
|
||||
<p id="statImages">–</p>
|
||||
<div class="progress-bar"><div id="barImages"></div></div>
|
||||
<h3>Images JPG 🖼️</h3>
|
||||
<p id="statImagesjpg">–</p>
|
||||
<div class="progress-bar"><div id="barImagesjpg"></div></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>Blank Card ✏️</h3>
|
||||
<p id="statImages">–</p>
|
||||
<div class="progress-bar"><div id="barnone"></div></div>
|
||||
<h3>Images RAW 🖼️</h3>
|
||||
<p id="statImagesraw">–</p>
|
||||
<div class="progress-bar"><div id="barImagesraw"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user