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
This commit is contained in:
2026-08-04 08:55:57 -05:00
parent c2ed5b3ffb
commit bbb56056c0
4 changed files with 117 additions and 18 deletions
+89 -1
View File
@@ -1,4 +1,35 @@
#!/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"
@@ -7,31 +38,54 @@ 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"
@@ -39,7 +93,9 @@ move_file() {
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++))
@@ -48,10 +104,11 @@ move_file() {
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/$year-$month"
dest_dir="$root/$year/$month"
dest="$dest_dir/$fname"
mkdir -p "$dest_dir"
if [[ -e "$dest" ]]; then
@@ -60,12 +117,43 @@ move_file() {
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"