diff --git a/01-rename.sh b/01-rename.sh deleted file mode 100755 index 625db2a..0000000 --- a/01-rename.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/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 deleted file mode 100755 index 9b6c4c4..0000000 --- a/02-convert.sh +++ /dev/null @@ -1,192 +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 (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 deleted file mode 100755 index 057a37f..0000000 --- a/03-pdf.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/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 new file mode 100644 index 0000000..15fbdc4 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +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 new file mode 100755 index 0000000..6a63e11 --- /dev/null +++ b/photos2pdf @@ -0,0 +1,433 @@ +#!/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 deleted file mode 100644 index 644844a..0000000 --- a/template/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*/*.svg diff --git a/template/a4paper/portrait.svg b/template/a4paper/portrait.svg new file mode 100644 index 0000000..4445b17 --- /dev/null +++ b/template/a4paper/portrait.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + ENTERPRISELOGO + Copyleft 2023 tk-sls.de +CC-BY-SA Attribution-ShareAlike 3.0 Unported + Generic Title Text + +