diff --git a/01-rename.sh b/01-rename.sh new file mode 100755 index 0000000..625db2a --- /dev/null +++ b/01-rename.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +## +# Main Program + +dir=$(dirname "$(readlink -f "$0")") +origdir="$dir/orig" +indir="$dir/in" + +if ! cd "$dir" ; then + echo "ERROR: Could not change to directory \"$dir\"; aborted." >&2 + exit 1 +elif ! test -d "$origdir" ; then + echo "ERROR: Directory \"$origdir\" not found; aborted." >&2 + exit 1 +elif ! mkdir -p "$indir" ; then + echo "ERROR: Could not create directory \"$indir\"; aborted." >&2 + exit 1 +elif ! rm -f -- "$indir/"*.jpg ; then + echo "ERROR: Could not remove files in \"$indir\"; aborted." >&2 + exit 1 +else + i=1 + rv=0 + + while read -r jpg ; do + renamed=$(printf "%02d" "$i").jpg + + if cp "$jpg" "$indir/$renamed" ; then + echo "INFO: Copied \"$jpg\" to \"$renamed\"." >&2 + else + echo "WARNING: Could not copy \"$jpg\" to \"$renamed\"." >&2 + rv=1 + fi + + i=$((i+1)) + done < <(ls --reverse --sort time -- "$origdir/"*.jpg) + + exit "$rv" +fi + diff --git a/02-convert.sh b/02-convert.sh new file mode 100755 index 0000000..9b6c4c4 --- /dev/null +++ b/02-convert.sh @@ -0,0 +1,192 @@ +#!/bin/bash + +## +# Configuration + +# Before morphology analysis, reduce image dimensions by this factor +simplify=2 + +# Morphology to apply to reduce content to main area +morphology="Open:6" +morphology_shape="Disk" + +# Number of colors images are quantized to for component detection +colors=4 + +# Neighbors to evaluate when detecting connected components +connected_components=8 + +# Area treshold for connected components detection +area_treshold=1000 + +# Sigmoidal contrast adjustment used when trimming +sigmoidal_contrast_trim="30,30%" + +# Sigmoidal contrast adjustment visible in result images +sigmoidal_contrast="45,50%" + +# Number of colors result images are quantized to +posterize=6 + +# Keep intermediate images after processing (note that they will be removed in +# any case at the next run of this script) +keep_tmpfiles=true + +## +# Main Program + +dir="$(dirname "$(readlink -f "$0")")" +tmpdir="$dir/tmp" +indir="$dir/in" +comdir="$dir/com" +outdir="$dir/out" +resize_percent=$((100/simplify)) + +if ! cd "$dir" ; then + echo "ERROR: Could not change to directory \"$dir\"; aborted." >&2 + exit 1 +elif ! mkdir -p "$comdir" "$outdir" "$tmpdir" ; then + echo "ERROR: Could not create working directories; aborted." >&2 + exit 1 +elif ! rm -f -- "$comdir/"*.* ; then + echo "ERROR: Could not delete existing component files; aborted." >&2 + exit 1 +elif ! rm -f -- "$tmpdir/"*.* ; then + echo "ERROR: Could not delete existing tempfiles; aborted." >&2 + exit 1 +elif ! rm -f -- "$outdir/"*.* ; then + echo "ERROR: Could not delete existing results; aborted." >&2 + exit 1 +else + rv=0 + + for jpg in "$indir/"*.jpg ; do + base=$(basename "$jpg" .jpg) + tmp1="$tmpdir/$base.tmp1.png" + tmp2="$tmpdir/$base.tmp2.png" + com="$tmpdir/$base.com.png" + trimmed="$tmpdir/$base.trimmed.png" + inf="$tmpdir/$base.txt" + out="$outdir/$base.png" + + if ! convert \ + "$jpg" \ + -colorspace RGB \ + -sigmoidal-contrast "$sigmoidal_contrast_trim" \ + -resize "$resize_percent%" \ + -colors "$colors" \ + -morphology "$morphology" "$morphology_shape" \ + "$tmp1" + then + echo "ERROR: Could not create test image \"$tmp1\"; skipped." >&2 + rv=1 + continue + fi + + draw=() + v=255 + + while read -r primitive ; do + draw+=("-fill" "rgb($v,$v,$v)" "-draw" "$primitive") + v=$((v-2)) + done < <( + convert \ + "$tmp1" \ + -define connected-components:verbose=true \ + -define connected-components:exclude-header=true \ + -define connected-components:sort-order=increasing \ + -define connected-components:area-threshold="$area_treshold" \ + -virtual-pixel None \ + -connected-components "$connected_components" \ + -auto-level \ + "$com" | \ + perl -e ' + while(<>){ + chomp; + next unless /(\d+)x(\d+)\+(\d+)\+(\d+)/; + printf + STDERR + "DEBUG: component: $_\n"; + printf + "rectangle %d,%d,%d,%d\n", + $1, $2, $1+$3, $2+$4; + } + + ' | \ + sort | \ + uniq + ) + + printf "DEBUG: connected components in \"%s\":" "$tmp1" + for d in "${draw[@]}" ; do + printf " \"%s\"" "$d" + done + printf "\n" + + if ! convert \ + "$tmp1" \ + -fill black -colorize 100 \ + "${draw[@]}" \ + "$tmp2" + then + echo "ERROR: Could not convert \"$tmp1\" to \"$tmp2\"; skipped." >&2 + rv=1 + continue + elif ! convert \ + "$tmp2" \ + -trim info: \ + > "$inf" + then + echo "ERROR: Could not determine trim info from \"$tmp2\"; skipped." >&2 + rv=1 + continue + fi + + if "$keep_tmpfiles" ; then + if ! convert \ + "$tmp2" \ + -trim \ + "$trimmed" + then + echo "ERROR: Could not test trim \"$tmp2\"; skipped." >&2 + rv=1 + continue + fi + fi + + # upscale crop area from smaller temporary picture sizes + crop=$(simplify=$simplify perl -MEnv -e ' + while(<>) { + chomp(); + s/(\d+)/$1*${simplify}/eg; + /(\d+)x(\d+) \d+x\d+\+(\d+)\+(\d+)/ && do { + print $1."x".$2."+".$3."+".$4; + } + } + ' < "$inf") + + if ! "$keep_tmpfiles" ; then + if ! rm -f -- "$tmp1" "$tmp2" "$trimmed" "$inf" ; then + echo "WARNING: Could not remove temporary files in \"$dir/tmp\"." >&2 + rv=1 + fi + fi + + echo "Processing $base: crop=$crop ..." + + if ! convert \ + "$jpg" \ + -crop "$crop" \ + -sigmoidal-contrast "$sigmoidal_contrast" \ + +dither -posterize "$posterize" \ + "$out" + then + echo "ERROR: Could not process \"$jpg\"; skipped." >&2 + rv=1 + continue + fi + done + + exit "$rv" +fi + diff --git a/03-pdf.sh b/03-pdf.sh new file mode 100755 index 0000000..057a37f --- /dev/null +++ b/03-pdf.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# shellcheck disable=SC2046 + +## +# Configuration + +# Paper size of all pages +paper="a4paper" + +# Offset in page where image should be placed +offset="1cm 3cm" + +# Factor to scale image by before placing in page +scale=0.75 + +# Filename of complete result PDF +result="pdf/result.pdf" + +## +# Main Program + +dir="$(dirname "$(readlink -f "$0")")" + +if ! cd "$dir" ; then + echo "ERROR: Could not change to directory \"$dir\"; aborted." >&2 + exit 1 +elif ! mkdir -p pdf tmp ; then + echo "ERROR: Could not create \"$dir/{pdf,tmp}\"; aborted." >&2 + exit 1 +elif ! rm -f -- "pdf/"*.* ; then + echo "ERROR: Could not delete existing results in \"$dir/pdf\"; aborted." >&2 + exit 1 +else + rv=0 + + for png in "out/"*.png ; do + echo "INFO: Processing \"$png\" ..." >&2 + + read -r width height < <( + identify -verbose "$png" | \ + perl -ne '/Geometry: (\d+)x(\d+)/ && print "$1 $2"' + ) + + if [[ -z $width ]] || [[ -z $height ]] ; then + echo "ERROR: Could not identify width or height of \"$png\"; skipped." >&2 + rv=1 + continue + elif [[ $width -gt $height ]] ; then + backdrop=template/a4paper/landscape.pdf + jam_landscape=--landscape + else + backdrop=template/a4paper/portrait.pdf + jam_landscape=--no-landscape + fi + + base=$(basename "$png" .png) + pdf=tmp/$base.pdf + jammed=tmp/$base-pdfjam.pdf + backdropped=tmp/$base.backdropped.pdf + + if ! convert "$png" "$pdf" ; then + echo "ERROR: Could not convert \"$png\" to PDF; skipped." >&2 + rv=1 + continue + elif ! pdfjam \ + --quiet \ + --outfile tmp \ + --paper "$paper" \ + "$jam_landscape" \ + --scale "$scale" \ + --offset "$offset" \ + "$pdf" + then + echo "ERROR: Could not align \"$pdf\"; skipped." >&2 + rv=1 + continue + elif ! pdftk \ + "$jammed" \ + stamp "$backdrop" \ + output "$backdropped" + then + echo "ERROR: Could not watermark \"$pdf\"; skipped." >&2 + rv=1 + continue + fi + done + + if ! pdftk $(ls -- "tmp/"*.backdropped.pdf) cat output "$result" ; then + echo "ERROR: Could not concatenate \"$result\"." >&2 + rv=1 + elif ! rm -f -- "tmp/"*.pdf ; then + echo "ERROR: Could not remove tempfiles in \"$dir/tmp\"." >&2 + rv=1 + fi + + exit "$rv" +fi + diff --git a/Makefile b/Makefile deleted file mode 100644 index 15fbdc4..0000000 --- a/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -NAME=photos2pdf -DESTDIR= -PREFIX=/usr -INSTALL=install - -BINDIR=$(PREFIX)/bin -LIBDIR=/var/lib/$(NAME) -TEMPLATEDIR=$(LIBDIR)/template - -.PHONY: all install-bin install-template install - -all: - @echo "Use \"sudo $(MAKE) install\" to install $(NAME)" - -install-bin: - $(INSTALL) -d -m 0755 "$(BINDIR)" - $(INSTALL) -m 0755 "$(NAME)" "$(BINDIR)/$(NAME)" - -install-template: - $(INSTALL) -d -m 0755 "$(DESTDIR)$(LIBDIR)" - $(INSTALL) -d -m 0755 "$(DESTDIR)$(TEMPLATEDIR)" - tar cf - -C template --exclude "*.svg" . | (cd "$(DESTDIR)$(TEMPLATEDIR)" && tar xf -) - -install: install-bin install-template diff --git a/photos2pdf b/photos2pdf deleted file mode 100755 index 6a63e11..0000000 --- a/photos2pdf +++ /dev/null @@ -1,433 +0,0 @@ -#!/bin/bash - -## -# Configuration - -# Before morphology analysis, reduce image dimensions by this factor -simplify=2 - -# Morphology to apply to reduce content to main area -morphology="Open:6" -morphology_shape="Disk" - -# Number of colors images are quantized to for component detection -colors=4 - -# Neighbors to evaluate when detecting connected components -connected_components=8 - -# Area treshold for connected components detection -area_treshold=1000 - -# Sigmoidal contrast adjustment used when trimming -sigmoidal_contrast_trim="30,30%" - -# Sigmoidal contrast adjustment visible in result images -sigmoidal_contrast="45,50%" - -# Number of colors result images are quantized to -posterize=6 - -# Keep intermediate images after processing -keep_tmpfiles=true - -# Paper size of all pages -paper="a4paper" - -# Offset in page where image should be placed -offset="1cm 3cm" - -# Factor to scale image by before placing in page -scale=0.75 - -# PDF backdrop template directory -templatedir=/var/lib/photos2pdf/template - -# Filename of complete result PDF -result_filename="result.pdf" - -# Directory of original files to process (initially unset) -dir= - -## -# Functions - -log() { - level=$1 - msg=$2 - - printf "%s: %s\n" "$level" "$msg" >&2 -} - -error() { - log ERROR "$1" - - exit "${2:-1}" -} - -warning() { - log WARNING "$1" -} - -info() { - log INFO "$1" -} - -print_help() { - cat << 'EOF' -photos2pdf -========== - -Description ------------ -Trim and brighten JPGs, produce PDF containing one photo per page. - -Usage ------ -```shell -photos2pdf [OPTION ...] DIR -``` - -Arguments ---------- -* `DIR`: Directory containing the original photos. - -Options -------- -* `--simplify INT`: Before morphology analysis, reduce - image dimensions by this factor - (default: 2). -* `--morphology STRING`: Morphology to apply for trimming - (default "Open:6"). -* `--morphology-shape STRING`: Shape of morphology to apply for - trimming (default: "Disk"). -* `--colors INT`: Number of colors images are quantized - to for component detection (default: 4). -* `--connected-components INT`: Neighbors to evaluate when detecting - connected components (default: 8). -* `--area_treshold INT`: Area treshold for connected components - detection (default: 1000). -* `--sigmoidal-contrast-trim STRING`: Sigmoidal contrast adjustment - used when trimming (default: "30,30%"). -* `--sigmoidal-contrast STRING`: Sigmoidal contrast adjustment visible - in result images (default: "45,50%"). -* `--posterize INT`: Number of colors result images are - quantized to (default: 6). -* `--keep-tmpfiles true|false`: Keep temporary files (default: false). -* `--paper STRING`: PDF page size (default: "a4paper"). -* `--offset STRING`: Offset in page where image should be - placed (default: "1cm 3cm"). -* `--scale FLOAT`: Factor to scale image by before placing - in page (default: 0.75). -* `--templatedir STRING`: PDF backdrop directory (default: - "/var/lib/photos2pdf/template"). -* `--result-filename STRING`: Filename of complete result PDF - (default: "result.pdf"). -EOF -} - -## -# Arguments - -i=1 -options_done=false - -while [[ $i -le $# ]] ; do - arg=$(eval "echo \$$i") - - if ! "$options_done" ; then - case "$arg" in - -h|--help) - print_help - exit 0 - ;; - --simplify| \ - --morphology| \ - --morphology-shape| \ - --colors| \ - --connected-components| \ - --area-treshold| \ - --sigmoidal-contrast-trim| \ - --sigmoidal-contrast| \ - --posterize| \ - --keep-tmpfiles| \ - --paper| \ - --offset| \ - --scale| \ - --templatedir| \ - --result-filename) - var=$(echo "$arg" | sed -e 's|^\-\-||;s|\-|_|g;') - i=$((i+1)) - arg=$(eval "echo \$$i") - eval "$var=\"$arg\"" - i=$((i+1)) - continue - ;; - --) - options_done=true - i=$((i+1)) - continue - ;; - -*) - error "Unknown option \"$arg\"; aborted." - ;; - esac - fi - - case "$arg" in - *) - [[ -n $dir ]] && error "Only one input directory can be specified; aborted." - dir=$arg - i=$((i+1)) - ;; - esac -done - -[[ -z $dir ]] && error "Usage: $0 [OPTION ..] DIR" - -## -# Main Program - -job_id=$(printf "%s" "$origdir" | sha256sum | cut -d" " -f1) - -origdir=$(readlink -f "$dir") - -[[ -d $origdir ]] || error "Directory $dir not found; aborted." - -if [[ -n $TMP ]] ; then - tmp_base=$TMP/photos2pdf/$job_id -elif [[ -n $XDG_RUNTIME_DIR ]] ; then - tmp_base=$XDG_RUNTIME_DIR/photos2pdf/$job_id -else - tmp_base=/tmp/photos2pdf/$job_id -fi - -indir="$tmp_base/in" -tmpdir="$tmp_base/tmp" -outdir="$tmp_base/out" -pdfdir="$tmp_base/pdf" - -if ! test -d "$origdir" ; then - error "Directory \"$origdir\" not found; aborted." -elif ! mkdir -p "$indir" "$outdir" "$pdfdir" "$tmpdir" ; then - error "Could not create working directories; aborted." -elif ! rm -f -- "$indir/"*.* ; then - error "Could not delete existing input files; aborted." -elif ! rm -f -- "$tmpdir/"*.* ; then - error "Could not delete existing tempfiles; aborted." -elif ! rm -f -- "$outdir/"*.* ; then - error "Could not delete existing results; aborted." -elif ! rm -f -- "$pdfdir/"*.* ; then - error "Could not delete existing PDFs; aborted." -fi - -resize_percent=$((100/simplify)) - -i=1 -rv=0 - -while read -r jpg ; do - renamed=$(printf "%02d" "$i").jpg - - if cp "$jpg" "$indir/$renamed" ; then - info "Copied \"$jpg\" to \"$renamed\"." - else - warning "Could not copy \"$jpg\" to \"$renamed\"." - rv=1 - fi - - i=$((i+1)) -done < <(ls --reverse --sort time -- "$origdir/"*.jpg) - -if [[ $rv -ne 0 ]] ; then - error "One or more errors during input file naming; aborted." "$rv" -fi - -for jpg in "$indir/"*.jpg ; do - base=$(basename "$jpg" .jpg) - tmp1="$tmpdir/$base.tmp1.png" - tmp2="$tmpdir/$base.tmp2.png" - com="$tmpdir/$base.com.png" - trimmed="$tmpdir/$base.trimmed.png" - inf="$tmpdir/$base.txt" - out="$outdir/$base.png" - - if ! convert \ - "$jpg" \ - -colorspace RGB \ - -sigmoidal-contrast "$sigmoidal_contrast_trim" \ - -resize "$resize_percent%" \ - -colors "$colors" \ - -morphology "$morphology" "$morphology_shape" \ - "$tmp1" - then - warning "Could not create test image \"$tmp1\"; skipped." - rv=1 - continue - fi - - draw=() - v=255 - - while read -r primitive ; do - draw+=("-fill" "rgb($v,$v,$v)" "-draw" "$primitive") - v=$((v-2)) - done < <( - convert \ - "$tmp1" \ - -define connected-components:verbose=true \ - -define connected-components:exclude-header=true \ - -define connected-components:sort-order=increasing \ - -define connected-components:mean-color=true \ - -define connected-components:area-threshold="$area_treshold" \ - -virtual-pixel None \ - -connected-components "$connected_components" \ - -auto-level \ - "$com" | \ - perl -e ' - while(<>){ - chomp; - next unless /(\d+)x(\d+)\+(\d+)\+(\d+)/; - printf - "rectangle %d,%d,%d,%d\n", - $1, $2, $1+$3, $2+$4; - } - - ' | \ - sort | \ - uniq - ) - - if ! convert \ - "$tmp1" \ - -fill black -colorize 100 \ - "${draw[@]}" \ - "$tmp2" - then - warning "Could not convert \"$tmp1\" to \"$tmp2\"; skipped." - rv=1 - continue - elif ! convert \ - "$tmp2" \ - -trim info: \ - > "$inf" - then - warning "Could not determine trim info from \"$tmp2\"; skipped." - rv=1 - continue - fi - - if "$keep_tmpfiles" ; then - if ! convert \ - "$tmp2" \ - -trim \ - "$trimmed" - then - warning "Could not test trim \"$tmp2\"; skipped." - rv=1 - continue - fi - fi - - # upscale crop area from smaller temporary picture sizes - crop=$(simplify=$simplify perl -MEnv -e ' - while(<>) { - chomp(); - s/(\d+)/$1*${simplify}/eg; - /(\d+)x(\d+) \d+x\d+\+(\d+)\+(\d+)/ && do { - print $1."x".$2."+".$3."+".$4; - } - } - ' < "$inf") - - info "Processing $base: crop=$crop ..." - - if ! convert \ - "$jpg" \ - -crop "$crop" \ - -sigmoidal-contrast "$sigmoidal_contrast" \ - +dither -posterize "$posterize" \ - "$out" - then - warning "Could not process \"$jpg\"; skipped." - rv=1 - continue - fi -done - -if [[ $rv -ne 0 ]] ; then - error "One or more errors during image conversion; aborted." "$rv" -fi - -for png in "out/"*.png ; do - info "Processing \"$png\" ..." - - read -r width height < <( - identify -verbose "$png" | \ - perl -ne '/Geometry: (\d+)x(\d+)/ && print "$1 $2"' - ) - - if [[ -z $width ]] || [[ -z $height ]] ; then - warning "Could not identify width or height of \"$png\"; skipped." - rv=1 - continue - elif [[ $width -gt $height ]] ; then - backdrop="$templatedir/a4paper/landscape.pdf" - jam_landscape=--landscape - else - backdrop="$templatedir/a4paper/portrait.pdf" - jam_landscape=--no-landscape - fi - - base=$(basename "$png" .png) - pdf="$tmpdir/$base.pdf" - jammed="$tmpdir/$base-pdfjam.pdf" - backdropped="$tmpdir/$base.backdropped.pdf" - - if ! convert "$png" "$pdf" ; then - warning "Could not convert \"$png\" to PDF; skipped." - rv=1 - continue - elif ! pdfjam \ - --quiet \ - --outfile "$tmpdir" \ - --paper "$paper" \ - "$jam_landscape" \ - --scale "$scale" \ - --offset "$offset" \ - "$pdf" - then - warning "Could not align \"$pdf\"; skipped." - rv=1 - continue - elif ! pdftk \ - "$jammed" \ - stamp "$backdrop" \ - output "$backdropped" - then - warning "Could not watermark \"$pdf\"; skipped." - rv=1 - continue - fi -done - -input=() - -while read -r pdf ; do - input+=("$pdf") -done < <(ls -- "$tmpdir/"*.backdropped.pdf) - -if ! pdftk "${input[@]}" cat output "$pdfdir/$result_filename" ; then - warning "Could not concatenate \"$result_filename\"." - rv=1 -elif ! "$keep_tmpfiles" ; then - if ! rm -f -- "$tmp_base" ; then - warning "Could not remove temporary files." - rv=1 - fi -fi - -if [[ $rv -ne 0 ]] ; then - error "One or more errors; aborted." "$rv" -fi - diff --git a/template/.gitignore b/template/.gitignore new file mode 100644 index 0000000..644844a --- /dev/null +++ b/template/.gitignore @@ -0,0 +1 @@ +*/*.svg diff --git a/template/a4paper/portrait.svg b/template/a4paper/portrait.svg deleted file mode 100644 index 4445b17..0000000 --- a/template/a4paper/portrait.svg +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - ENTERPRISELOGO - Copyleft 2023 tk-sls.de -CC-BY-SA Attribution-ShareAlike 3.0 Unported - Generic Title Text - -