Files
homelab-configs/owncloud/organize_photos.sh
T
jgitta bbb56056c0 owncloud: document and fix organize_photos.sh folder structure
- Add full header doc block explaining purpose, patterns, and scheduling
- Inline comments on all functions and both processing passes
- Fix folder format: YYYY/YYYY-MM/ → YYYY/MM/ (year was redundant in subfolder)
- Add pass 2: migrate existing YYYY/YYYY-MM/ folders to YYYY/MM/
- Pass 1 now also handles Android-app pre-sorted YYYY/MM/ videos correctly

Also commit pending doc updates:
- README.md: remove Kopia stack, add ntfy entry, fix infrastructure.caddy note
- authentik/SETUP-GUIDE.md: add Mealie OIDC setup notes
- homelab-overview.md: remove Kopia refs, add Vaultwarden, fix TrueNAS/PBS wording
2026-08-04 08:55:57 -05:00

161 lines
6.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
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.
#!/usr/bin/env bash
# =============================================================================
# organize_photos.sh
# =============================================================================
# Organizes files in the ownCloud Instant Upload folders into a consistent
# date-based folder structure: SUBDIR/YYYY/MM/filename
#
# The ownCloud Android app pre-sorts videos into YYYY/MM/ subfolders
# automatically, but uses YYYY/YYYY-MM/ for images. This script:
# 1. Moves any new files dropped in the SUBDIR root into YYYY/MM/
# 2. Migrates old YYYY/YYYY-MM/ folders → YYYY/MM/ (removes redundant year)
#
# Processed subdirectories (under BASE/):
# Camera phone photos and videos (instant upload)
# BlueIris BlueIris NVR clip exports
# Facebook Facebook photo downloads
# scans scanned documents
# EufyVideoDir Eufy security camera recordings
#
# Files with no recognisable date go to BASE/_unsorted/ unchanged.
#
# Date extraction tries these patterns in order (first match wins):
# 1. YYYYMMDD embedded anywhere in the filename (e.g. PXL_20260802_...)
# 2. YYYY-MM-DD or YYYY_MM_DD separator format (e.g. 2026-08-02)
# 3. 13-digit Unix millisecond timestamp (e.g. 1722614400000)
# 4. MM_DD_YYYY_ prefix (e.g. 08_02_2026_clip)
#
# Scheduling: run via cron or systemd timer. Example cron (every 15 min):
# */15 * * * * /path/to/organize_photos.sh
#
# Log file: /var/log/organize_photos.log
# =============================================================================
BASE="/mnt/INTEL-SSD/ownCloud - Joe Gitta@cloud.jgitta.com/Personal/InstantUpload"
UNSORTED="$BASE/_unsorted"
LOG="/var/log/organize_photos.log"
moved=0; skipped=0; unsorted=0; errors=0
echo "=== $(date +%Y-%m-%d\ %H:%M:%S) Starting ===" >> "$LOG"
# -----------------------------------------------------------------------------
# ts_to_ym TIMESTAMP_MS
# Converts a 13-digit Unix millisecond timestamp to "YYYY MM".
# -----------------------------------------------------------------------------
ts_to_ym() {
local ts_ms="$1"
local ts_s=$(( ts_ms / 1000 ))
date -d "@$ts_s" "+%Y %m" 2>/dev/null
}
# -----------------------------------------------------------------------------
# get_ym FILEPATH
# Extracts "YYYY MM" from a filename using the patterns described in the header.
# Prints "YYYY MM" on success, empty string if no date found.
# -----------------------------------------------------------------------------
get_ym() {
local f="$1"
local b
b=$(basename "$f")
# Pattern 1: YYYYMMDD anywhere in filename (e.g. PXL_20260802_171610514.mp4)
if [[ "$b" =~ (^|[^0-9])([0-9]{4})(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01]) ]]; then
echo "${BASH_REMATCH[2]} ${BASH_REMATCH[3]}"; return
fi
# Pattern 2: YYYY-MM-DD or YYYY_MM_DD (e.g. 2026-08-02_clip.mp4)
if [[ "$b" =~ ([0-9]{4})[-_](0[1-9]|1[0-2])[-_](0[1-9]|[12][0-9]|3[01]) ]]; then
echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]}"; return
fi
# Pattern 3: 13-digit Unix ms timestamp (e.g. 1722614400000_eufy.mp4)
if [[ "$b" =~ (^|[^0-9])([0-9]{13})([^0-9]|$) ]]; then
ts_to_ym "${BASH_REMATCH[2]}"; return
fi
# Pattern 4: MM_DD_YYYY_ prefix (e.g. 08_02_2026_doorbell.mp4)
if [[ "$b" =~ ^([0-9]{2})_([0-9]{2})_([0-9]{4})_ ]]; then
echo "${BASH_REMATCH[3]} ${BASH_REMATCH[1]}"; return
fi
echo ""
}
# -----------------------------------------------------------------------------
# move_file SRC ROOT
# Moves SRC into ROOT/YYYY/MM/filename using the date extracted from the
# filename. Sends undatable files to UNSORTED. Skips if destination exists.
# -----------------------------------------------------------------------------
move_file() {
local src="$1"
local root="$2"
local fname
fname=$(basename "$src")
local ym
ym=$(get_ym "$src")
if [[ -z "$ym" ]]; then
# No date found — move to _unsorted for manual review
mkdir -p "$UNSORTED"
if [[ ! -e "$UNSORTED/$fname" ]]; then
mv "$src" "$UNSORTED/$fname" && ((unsorted++)) || ((errors++))
else
((skipped++))
fi
return
fi
local year month dest_dir dest
year=$(echo "$ym" | cut -d' ' -f1)
month=$(echo "$ym" | cut -d' ' -f2)
dest_dir="$root/$year/$month"
dest="$dest_dir/$fname"
mkdir -p "$dest_dir"
if [[ -e "$dest" ]]; then
((skipped++)); return
fi
mv "$src" "$dest" && ((moved++)) || { echo " ERROR: $fname" >> "$LOG"; ((errors++)); }
}
# =============================================================================
# Main loop — process each instant-upload subdirectory
# =============================================================================
for SUBDIR in Camera BlueIris Facebook scans EufyVideoDir; do
SUBPATH="$BASE/$SUBDIR"
[[ -d "$SUBPATH" ]] || continue
# --- Pass 1: files dropped directly into the subdir root ----------------
# These are new uploads not yet organised by the Android app.
while IFS= read -r -d '' f; do
move_file "$f" "$SUBPATH"
done < <(find "$SUBPATH" -maxdepth 1 -type f -print0)
# --- Pass 2: migrate old YYYY/YYYY-MM/ folders → YYYY/MM/ ---------------
# Older versions of this script (and some ownCloud client versions) created
# YYYY/YYYY-MM/ subfolders, which redundantly repeat the year. This pass
# flattens them to YYYY/MM/ and removes the now-empty YYYY-MM directory.
for year_dir in "$SUBPATH"/[0-9][0-9][0-9][0-9]; do
[[ -d "$year_dir" ]] || continue
year=$(basename "$year_dir")
for old_dir in "$year_dir"/"$year"-[0-9][0-9]; do
[[ -d "$old_dir" ]] || continue
month="${old_dir##*-}" # extract MM from YYYY-MM folder name
dest_dir="$SUBPATH/$year/$month"
mkdir -p "$dest_dir"
while IFS= read -r -d '' f; do
fname=$(basename "$f")
dest="$dest_dir/$fname"
if [[ ! -e "$dest" ]]; then
mv "$f" "$dest" && ((moved++)) || { echo " ERROR: $fname" >> "$LOG"; ((errors++)); }
else
((skipped++))
fi
done < <(find "$old_dir" -maxdepth 1 -type f -print0)
rmdir "$old_dir" 2>/dev/null # Remove if now empty
done
done
done
echo " Moved: $moved Unsorted: $unsorted Skipped: $skipped Errors: $errors" >> "$LOG"
echo "=== Done ===" >> "$LOG"