# Album EXIF presentation: turn a photo's parsed EXIF values into the per-photo # details table and hover tooltip the album pages render. This module now holds # ONLY the EXIF presentation concern -- the file counting, tarball naming, # generation-metadata/JSON, dry-run and cache-lifecycle helpers that used to live # here were split out into their own modules (task 6r0): counting -> image, tarball # naming -> archive, generation metadata -> generation-metadata, dry-run -> dry-run, # clear_exif_cache -> metadata-cache. # # The EXIF identify cache primitive (cached_photo_identify_output and its # private helpers photo_cache_signature / print_cached_photo_identify_output) # was promoted to the shared metadata-cache.source.sh module (task pn0): it is a # low-level metadata primitive consumed by both this album module and the stats # aggregator, so it no longer belongs to album internals. The helpers below call # cached_photo_identify_output through that shared module. photo_exif_details_html() { local -r photo="$1"; shift local -r photo_path="$1"; shift # exif_values is filled via the shared parser and iterated below. # shellcheck disable=SC2034 local -A exif_values=() local key local key_html local value local value_html local -i exif_count=0 # Parse via the single canonical reader/parser (task 8r0) instead of a # private exif: regex loop. The parser also yields a synthetic __geometry key # from the native Geometry line; the details table only shows real exif: tags # (rendered with their historical "exif:" label prefix), so __geometry is # skipped explicitly below. _photo_exif_values_to exif_values "$photo" "$photo_path" for key in "${!exif_values[@]}"; do if [ "$key" = '__geometry' ]; then continue fi value="${exif_values[$key]}" key_html=$(html_escape "exif:$key") value_html=$(html_escape "$value") if (( exif_count == 0 )); then printf '\n' printf '\n' fi printf '\n' \ "$key_html" \ "$value_html" (( ++exif_count )) done if (( exif_count == 0 )); then printf '

No EXIF details available.

\n' return fi printf '\n' printf '
%s%s
\n' } # output_ref is a string nameref here. Other modules (e.g. # metadata-cache.source.sh) reuse the name "output_ref" as an associative array, # so --check-sourced cross-file nameref aliasing misreports each string write as # an array assignment (SC2178). Both writes below are correct string nameref # assignments, so SC2178 is suppressed at each one. _first_exif_value_to() { # shellcheck disable=SC2178 local -n output_ref="$1"; shift local -n exif_ref="$1"; shift local key # shellcheck disable=SC2178 output_ref='' for key in "$@"; do if [ -n "${exif_ref[$key]:-}" ]; then # shellcheck disable=SC2034,SC2178 output_ref="${exif_ref[$key]}" return fi done } # Fill the caller's associative array with a photo's parsed EXIF values. Thin # wrapper that pairs the shared cache reader with the shared stream parser # (photo_exif_values_to, promoted to metadata-cache.source.sh in task 8r0): it # reads the cached identify output for this photo and pipes it through the one # canonical parser. The parser also captures a __geometry key, which album code # simply ignores. _photo_exif_values_to() { local -r target_array="$1"; shift local -r photo="$1"; shift local -r photo_path="$1"; shift photo_exif_values_to "$target_array" \ < <(cached_photo_identify_output "$photo" "$photo_path") } # Build the ordered "Label: value" tooltip parts from a parsed EXIF values map. # Reads the values map by NAME (exif_name) and appends to the parts array passed # by NAME (parts_name); only present fields are added, preserving the original # Camera/Aperture/ISO/Shutter speed/Taken order. The nameref names are unique so # they cannot collide with the caller's own variable names. _collect_exif_tooltip_parts() { local -r exif_name="$1"; shift local -r parts_name="$1"; shift # shellcheck disable=SC2178 local -n collect_parts_ref="$parts_name" local -n collect_values_ref="$exif_name" local aperture local camera local date_time local iso local make local model local shutter_speed make="${collect_values_ref[Make]:-}" model="${collect_values_ref[Model]:-}" # Dedup the manufacturer prefix via the shared helper (task mn0) so this # tooltip and the stats leaderboard derive identical camera labels. camera=$(camera_label_from_make_model "$make" "$model") _first_exif_value_to aperture "$exif_name" FNumber ApertureValue _first_exif_value_to iso "$exif_name" \ ISOSpeedRatings PhotographicSensitivity ISO _first_exif_value_to shutter_speed "$exif_name" \ ExposureTime ShutterSpeedValue _first_exif_value_to date_time "$exif_name" \ DateTimeOriginal DateTimeDigitized DateTime if [ -n "$camera" ]; then collect_parts_ref+=("Camera: $camera") fi if [ -n "$aperture" ]; then collect_parts_ref+=("Aperture: $aperture") fi if [ -n "$iso" ]; then collect_parts_ref+=("ISO: $iso") fi if [ -n "$shutter_speed" ]; then collect_parts_ref+=("Shutter speed: $shutter_speed") fi if [ -n "$date_time" ]; then collect_parts_ref+=("Taken: $date_time") fi } # Print the collected tooltip parts joined by "; ", with a trailing newline only # when at least one part exists (so an EXIF-less photo prints nothing). Reads the # parts array by NAME. _emit_exif_tooltip_parts() { local -r parts_name="$1"; shift local -n emit_parts_ref="$parts_name" local key local separator='' for key in "${emit_parts_ref[@]}"; do printf '%s%s' "$separator" "$key" separator='; ' done if (( ${#emit_parts_ref[@]} > 0 )); then printf '\n' fi } # Thin orchestrator: collect the EXIF tooltip parts from a parsed values map, # then emit them. Output is byte-identical to the previous single function. _photo_exif_tooltip_text_from_values() { local -r exif_name="$1"; shift # Populated by _collect_exif_tooltip_parts via nameref; shellcheck cannot see # that cross-function write. # shellcheck disable=SC2034 local -a tooltip_parts=() _collect_exif_tooltip_parts "$exif_name" tooltip_parts _emit_exif_tooltip_parts tooltip_parts } photo_exif_tooltip_text() { local -r photo="$1"; shift local -r photo_path="$1"; shift # exif_values is populated and read through nameref helpers. # shellcheck disable=SC2034 local -A exif_values=() _photo_exif_values_to exif_values "$photo" "$photo_path" _photo_exif_tooltip_text_from_values exif_values } # A photo's EXIF date-taken, in the raw "YYYY:MM:DD HH:MM:SS" EXIF format. # Consumed by CHRONOLOGICAL_ORDER (album-photo-select.source.sh, # chronological_sort_key_for_photo) so ordering by "date taken" uses exactly the # same tag fallback chain (DateTimeOriginal -> DateTimeDigitized -> DateTime) as # the tooltip's "Taken:" field above -- one photo can't disagree with itself # about when it was taken depending on which feature asks. Empty when none of # the three tags is present (e.g. screenshots, downloaded images); callers # needing a value in that case supply their own deterministic fallback rather # than guessing a date here. photo_date_taken() { local -r photo="$1"; shift local -r photo_path="$1"; shift # exif_values is populated and read through nameref helpers. # shellcheck disable=SC2034 local -A exif_values=() local date_time _photo_exif_values_to exif_values "$photo" "$photo_path" _first_exif_value_to date_time exif_values \ DateTimeOriginal DateTimeDigitized DateTime printf '%s\n' "$date_time" }