]> zdv2.bktei.com Git - BK-2020-03.git/commitdiff
feat(user/gthumb/gthumb-analyze.sh):Get stats on selected images develop
authorSteven Baltakatei Sandoval <baltakatei@gmail.com>
Tue, 11 Aug 2026 18:53:33 +0000 (18:53 +0000)
committerSteven Baltakatei Sandoval <baltakatei@gmail.com>
Tue, 11 Aug 2026 18:53:33 +0000 (18:53 +0000)
user/gthumb/gthumb-analyze.sh [new file with mode: 0644]

diff --git a/user/gthumb/gthumb-analyze.sh b/user/gthumb/gthumb-analyze.sh
new file mode 100644 (file)
index 0000000..430b77d
--- /dev/null
@@ -0,0 +1,140 @@
+#!/bin/bash
+# Desc: Analyzes photo files selected in gThumb; shows size/time/GPS
+#   statistics in a zenity dialog. Intended for evaluating burst-mode
+#   sequences where larger JPEG size correlates with sharpness.
+# Usage: gthumb-analyze.sh FILE…
+# Example: $HOME/.local/bin/gthumb-analyze.sh %F
+# Depends: bash, GNU coreutils, awk, bc, column (util-linux 2.39.3)
+#   exiftool (12.x), GNU sed 4.9, zenity
+# Version: 0.0.9
+
+fdebug=/tmp/gtAnalyzeLog.txt;
+
+yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
+die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
+must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
+main() {
+    # Validate inputs
+    local fina fin cmd;
+    fina=("$@");
+    if [[ $# -le 0 ]]; then die "FATAL:No arguments:$#"; fi;
+    for fin in "${fina[@]}"; do
+        if [[ ! -f "$fin" ]]; then die "FATAL:Not a file:${fin}"; fi;
+    done;
+    for cmd in awk bc column cut exiftool sed sort zenity; do
+        command -v "$cmd" >/dev/null 2>&1 || die "FATAL:Missing dependency:${cmd}";
+    done;
+
+    # Single batched exiftool pass. Columns (tab-separated):
+    #   col 1: FileName
+    #   col 2: FileSize [bytes]
+    #   col 3: SubSecCreateDate [float POSIX seconds]
+    #   col 4: GPSPosition [±Lat, ±Lon]
+    #   col 5: FNumber [float]
+    #   col 6: ExposureTime [str seconds]
+    #   col 7: ISO [int]
+    #   col 8: FocalLength [str %.01f mm]
+    # Note: Missing tags emit '-' in -T mode.
+    local tsv;
+    tsv="$(exiftool -q -T -FileName '-FileSize#' \
+        -SubSecCreateDate -d '%s%f' \
+        -GPSPosition -c '%+0.4f' \
+        -FNumber -ExposureTime -ISO -FocalLength \
+        -- "${fina[@]}")" \
+        || die "FATAL:exiftool failed";
+    yell "DEBUG:tsv:"; printf '%s\n' "$tsv" 1>&2;
+
+    local n fMax fMaxArr fMin fMinArr mean sdev median span gps;
+    n="$(printf '%s\n' "$tsv" | wc -l)";
+
+    # First and last by file name
+    local fFirst fLast;
+    fFirst="$(printf '%s\n' "$tsv" | sort -t$'\t' -k1,1 | head -n1 | cut -f1)";
+    fLast="$(printf '%s\n' "$tsv" | sort -t$'\t' -k1,1 | tail -n1 | cut -f1)";
+    
+    # Largest and smallest files by size (numeric sort on column 2)
+    fMax="$(printf '%s\n' "$tsv" | sort -t$'\t' -k2,2n | tail -n1)";
+    fMin="$(printf '%s\n' "$tsv" | sort -t$'\t' -k2,2n | head -n1)";
+    fMaxArr[0]="$(cut -f1 <<< "$fMax")";
+    fMaxArr[1]="$(cut -f2 <<< "$fMax")";
+    fMinArr[0]="$(cut -f1 <<< "$fMin")";
+    fMinArr[1]="$(cut -f2 <<< "$fMin")";
+    ## Avoid & or < being parsed as markup by zenity
+    fMaxArr[0]="$(sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' <<< "${fMaxArr[0]}")";
+    fMinArr[0]="$(sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' <<< "${fMinArr[0]}")";
+
+    # Mean and population standard deviation (÷N, not N−1: the
+    #   selection is the entire population of interest).
+    read -r mean sdev < <(printf '%s\n' "$tsv" | cut -f2 \
+        | awk '{s+=$1; ss+=$1*$1;} END {m=s/NR; printf "%.0f %.0f\n", m, sqrt(ss/NR - m*m);}');
+
+    # Median file size
+    median="$(printf '%s\n' "$tsv" | cut -f2 | sort -n \
+    | awk '{a[i++]=$1;} END {if (i%2==0) {printf "%.0f\n", (a[i/2-1]+a[i/2])/2;} else {print a[int(i/2)];};}')";
+
+    # Time span in seconds via bc; ignore files lacking dates.
+    local tMin tMax;
+    tMin="$(printf '%s\n' "$tsv" | cut -f3 | grep -v '^-$' | sort -n | head -n1)";
+    tMax="$(printf '%s\n' "$tsv" | cut -f3 | grep -v '^-$' | sort -n | tail -n1)";
+    if [[ -n "$tMin" && -n "$tMax" ]]; then
+        span="$(printf "%'.3f s" "$(bc -l <<< "${tMax} - ${tMin}")")"; # seconds
+    else
+        span="n/a (missing SubSecCreateDate)";
+    fi;
+
+    # GPS: single unique value, none, or multiple.
+    local gpsUniq gpsUniqSamp gpsCount;
+    gpsUniq="$(printf '%s\n' "$tsv" | cut -f4 | sort -u | grep -v '^-$')";
+    gpsCount="$(printf '%s\n' "$gpsUniq" | grep -c .)";
+    case "$gpsCount" in
+        0) gps="none";;
+        *) gps="(${gpsCount} distinct)";
+           mapfile -t gpsUniqSamp < <(shuf <<< "$gpsUniq" | head -n8 | sort);;
+    esac;
+
+    # Exposure combos: unique (FNumber, ExposureTime, ISO, FocalLength) tuples.
+    # Missing tags appear as '-' and still compare consistently.
+    local expUniq expUniqSamp expCount exp;
+    expUniq="$(printf '%s\n' "$tsv" | cut -f5-8 | sort -u \
+        | awk -F'\t' '{printf "f/%s\t%s s\tISO %s\t%s\n", $1, $2, $3, $4;}')";
+    mapfile -t expUniqSamp < <(shuf <<< "$expUniq" | head -n8 | sort -t $'\t' -k4,4n);
+    
+    expCount="$(printf '%s\n' "$expUniq" | grep -c .)";
+    case "$expCount" in
+        1) exp="uniform";;
+        *) exp="${expCount} distinct combinations";;
+    esac;
+    
+    # Report
+    local msgSec1Arr msgSec1 msgSec2Arr msgSec2 msgSec3Arr msgSec3 msg;
+    ## Assemble message section 1 (file size, time span)
+    msgSec1Arr+=("$(printf "%d files analyzed (About %d s)" "$n" "$SECONDS")");
+    msgSec1Arr+=("$(printf "First:\t\t%s" "$fFirst")");
+    msgSec1Arr+=("$(printf "Last:\t\t%s" "$fLast")");
+    msgSec1Arr+=("$(printf "%s" "----" )");
+    msgSec1Arr+=("$(printf "Largest:\t%'d B\t%s" "${fMaxArr[1]}" "${fMaxArr[0]}")");
+    msgSec1Arr+=("$(printf "Smallest:\t%'d B\t%s" "${fMinArr[1]}" "${fMinArr[0]}")");
+    msgSec1Arr+=("$(printf "Mean size:\t%'d B" "$mean")");
+    msgSec1Arr+=("$(printf "Median size:\t%'d B" "$median")");
+    msgSec1Arr+=("$(printf "Std. dev.:\t%'d B" "$sdev")");
+    msgSec1Arr+=("$(printf "Time span:\t%s" "$span")");
+    msgSec1="$(printf '%s\n' "${msgSec1Arr[@]}" | column -s $'\t' -t --table-right 2)";
+    ## Assemble message section 2 (location)
+    msgSec2Arr+=("$(printf "GPS:\t%s" "$gps")");
+    msgSec2Arr+=("$(printf '%s\n' "${gpsUniqSamp[@]}")");
+    msgSec2="$(printf '%s\n' "${msgSec2Arr[0]}"; printf '%s\n' "${msgSec2Arr[@]:1}" | column -s $'\t' -t | sed -e 's/^/\t/')";    
+    ## Assemble message section 3 (exposure)
+    msgSec3Arr+=("$(printf "Exposure:\t%s" "$exp")");
+    msgSec3Arr+=("$(printf '%s\n' "${expUniqSamp[@]}")");
+    msgSec3="$(printf '%s\n' "${msgSec3Arr[0]}"; printf '%s\n' "${msgSec3Arr[@]:1}" | column -s $'\t' -t | sed -e 's/^/\t/')";
+    ## Assemble combined message
+    msg="$(printf '%s\n--------\n' "$msgSec1" "$msgSec2" "$msgSec3")";
+    ## Display message
+    zenity --info --title="gThumb-analyze.sh stats" --no-wrap --text="<span font='monospace'>${msg}</span>";
+};
+
+main "$@" 1>>"$fdebug" 2>&1;
+
+# Author: Steven Baltakatei Sandoval
+# License: GPLv3+
+