M Makefile => Makefile +8 -0
@@ 17,8 17,10 @@ install:
$(call subdir_make,documents,install)
$(call subdir_make,emulation,install)
$(call subdir_make,games,install)
+ $(call subdir_make,images,install)
$(call subdir_make,network,install)
$(call subdir_make,sound,install)
+ $(call subdir_make,videos,install)
uninstall:
$(call subdir_make,core,uninstall)
@@ 28,8 30,10 @@ uninstall:
$(call subdir_make,documents,uninstall)
$(call subdir_make,emulation,uninstall)
$(call subdir_make,games,uninstall)
+ $(call subdir_make,images,uninstall)
$(call subdir_make,network,uninstall)
$(call subdir_make,sound,uninstall)
+ $(call subdir_make,videos,uninstall)
test: clean
$(call subdir_make,core,test)
@@ 39,8 43,10 @@ test: clean
$(call subdir_make,documents,test)
$(call subdir_make,emulation,test)
$(call subdir_make,games,test)
+ $(call subdir_make,images,test)
$(call subdir_make,network,test)
$(call subdir_make,sound,test)
+ $(call subdir_make,videos,test)
clean:
$(call subdir_make,core,clean)
@@ 50,6 56,8 @@ clean:
$(call subdir_make,documents,clean)
$(call subdir_make,emulation,clean)
$(call subdir_make,games,clean)
+ $(call subdir_make,images,clean)
$(call subdir_make,network,clean)
$(call subdir_make,sound,clean)
+ $(call subdir_make,videos,clean)
M core/Makefile => core/Makefile +2 -0
@@ 7,6 7,7 @@ COMP_DIR?=/usr/local/etc/bash_completion.d
install:
install -m755 ctdir $(BIN_DIR)/ctdir
install -m755 debom $(BIN_DIR)/debom
+ install -m755 dired $(BIN_DIR)/dired
install -m755 enumerate $(BIN_DIR)/enumerate
install -m755 gitstat $(BIN_DIR)/gitstat
install -m755 mkbak $(BIN_DIR)/mkbak
@@ 32,6 33,7 @@ install:
uninstall:
rm $(BIN_DIR)/ctdir
+ rm $(BIN_DIR)/dired
rm $(BIN_DIR)/debom
rm $(BIN_DIR)/enumerate
rm $(BIN_DIR)/gitstat
A core/dired => core/dired +42 -0
@@ 0,0 1,42 @@
+#!/bin/sh
+
+name="dired"
+version="1.0"
+usage_message="Usage: dired [TARGET]"
+help_message=$(/usr/bin/cat <<-EOF
+ Open Emacs in dired mode
+ Usage: dired [TARGET]
+ Options:
+ -h, --help print this message and exit
+ -q, --quiet suppress error messages
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+if [ "$#" -eq 0 ]; then
+ exec emacs -nw .
+elif [ "$#" -eq 1 ] && [ "$quiet" -eq 1 ]; then
+ exit 1
+fi
+
+# loop through arguments
+for arg; do
+ case "$arg" in
+ -q|--quiet)
+ #ignore these
+ ;;
+
+ *)
+ if [ ! -d "$arg" ]; then
+ if [ "$quiet" -eq 0 ]; then
+ (>&2 /usr/bin/printf "%s: No such directory '%s'\n" "$name" "$arg")
+ fi
+ exit 1
+ fi
+ exec emacs -nw "$arg"
+ ;;
+ esac
+done
+
A core/git-blobs => core/git-blobs +24 -0
@@ 0,0 1,24 @@
+#!/bin/sh
+
+name="git-blobs"
+version="1.0"
+usage_message="Usage: git-big [OPTIONS]"
+help_message=$(/usr/bin/cat <<-EOF
+ List blobs in git history
+ Usage: git-blob [OPTIONS]
+ Options:
+ -h, --help print this message and exit
+ -q, --quiet suppress error messages
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+git rev-list --objects --all |
+ git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
+ sed -n 's/^blob //p' |
+ sort --numeric-sort --key=2 |
+ cut -c 1-12,41- |
+ numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest
+
A core/test/dired.bats => core/test/dired.bats +138 -0
@@ 0,0 1,138 @@
+#!/usr/bin/env bats
+bats_require_minimum_version 1.5.0
+
+@test "dired version" {
+ run --separate-stderr dired --version
+ [ "$status" -eq 0 ]
+ [ "$output" = "dired 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired version - quiet" {
+ run --separate-stderr dired --version --quiet
+ [ "$status" -eq 0 ]
+ [ "$output" = "dired 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired version - quiet short" {
+ run --separate-stderr dired --version -q
+ [ "$status" -eq 0 ]
+ [ "$output" = "dired 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired version short" {
+ run --separate-stderr dired -v
+ [ "$status" -eq 0 ]
+ [ "$output" = "dired 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired version short - quiet" {
+ run --separate-stderr dired -v --quiet
+ [ "$status" -eq 0 ]
+ [ "$output" = "dired 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired version short - quiet short" {
+ run --separate-stderr dired -v -q
+ [ "$status" -eq 0 ]
+ [ "$output" = "dired 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired help" {
+ run --separate-stderr dired --help
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Open Emacs in dired mode" ]
+ [ "${lines[1]}" = "Usage: dired [TARGET]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message and exit" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages" ]
+ [ "${lines[5]}" = " -v, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired help - quiet" {
+ run --separate-stderr dired --help --quiet
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Open Emacs in dired mode" ]
+ [ "${lines[1]}" = "Usage: dired [TARGET]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message and exit" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages" ]
+ [ "${lines[5]}" = " -v, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired help - quiet short" {
+ run --separate-stderr dired --help -q
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Open Emacs in dired mode" ]
+ [ "${lines[1]}" = "Usage: dired [TARGET]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message and exit" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages" ]
+ [ "${lines[5]}" = " -v, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired help short" {
+ run --separate-stderr dired -h
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Open Emacs in dired mode" ]
+ [ "${lines[1]}" = "Usage: dired [TARGET]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message and exit" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages" ]
+ [ "${lines[5]}" = " -v, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired help short - quiet" {
+ run --separate-stderr dired -h --quiet
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Open Emacs in dired mode" ]
+ [ "${lines[1]}" = "Usage: dired [TARGET]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message and exit" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages" ]
+ [ "${lines[5]}" = " -v, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired help short - quiet short" {
+ run --separate-stderr dired -h -q
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Open Emacs in dired mode" ]
+ [ "${lines[1]}" = "Usage: dired [TARGET]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message and exit" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages" ]
+ [ "${lines[5]}" = " -v, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired no such directory" {
+ run --separate-stderr dired foobarbaz
+ [ "$status" -eq 1 ]
+ [ "$output" = "" ]
+ [ "$stderr" = "dired: No such directory 'foobarbaz'" ]
+}
+
+@test "dired no such directory - quiet" {
+ run --separate-stderr dired foobarbaz --quiet
+ [ "$status" -eq 1 ]
+ [ "$output" = "" ]
+ [ "$stderr" = "" ]
+}
+
+@test "dired no such directory - quiet short" {
+ run --separate-stderr dired foobarbaz -q
+ [ "$status" -eq 1 ]
+ [ "$output" = "" ]
+ [ "$stderr" = "" ]
+}
+
A hardware/gpu-ls => hardware/gpu-ls +10 -0
@@ 0,0 1,10 @@
+#!/bin/sh
+
+header="Name:GPU:Memory
+"
+table="$(nvidia-smi --query-gpu name,utilization.gpu,utilization.memory --format=csv \
+ | sed 's/ %/%/g' \
+ | awk 'BEGIN{FS=", "; OFS=":"} {if(NR>1) print $1,$2,$3}')"
+
+cat <<<"${header}${table}" | column --separator=":" --table
+
A hardware/gpu-ps => hardware/gpu-ps +24 -0
@@ 0,0 1,24 @@
+#!/bin/bash
+
+# nvidia gpu query
+query="$(nvidia-smi pmon --count=1 --select=m | sed 's/# /#/')"
+memunit="$(awk '{if(NR==2) print $4}' <<<"$query")"
+
+# nvidia table construction
+full_header="::Memory Usage:(in ${memunit})
+PID:Process:Frame Buffer:Confidential Compute
+"
+full_table="$(awk 'BEGIN{OFS=":"} {if(NR>2) print $2,$6,$4,$5}' <<<"$query")"
+
+abbrev_header="PID:Process:Memory Usage (${memunit})
+"
+abbrev_table="$(awk 'BEGIN{OFS=":"} {if(NR>2) print $2,$6,$4}' <<<"$query")"
+
+# switch on whether there is any confidential compute memory usage
+ccmemusage="$(awk '{if(NR>2) sum+=$5} END{print sum}' <<<"$query")"
+if [[ "$ccmemusage" -eq 0 ]]; then
+ cat <<<"${abbrev_header}${abbrev_table}" | column --separator=':' --table
+else
+ cat <<<"${full_header}${full_table}" | column --separator=':' --table
+fi
+
A images/Makefile => images/Makefile +15 -0
@@ 0,0 1,15 @@
+BIN_DIR?=/usr/local/bin
+LIB_DIR?=/usr/local/lib
+COMP_DIR?=/usr/local/etc/bash_completion.d
+
+.PHONY: install uninstall test clean
+
+install:
+ install -m755 pdf-concat $(BIN_DIR)/pdf-concat
+
+uninstall:
+ rm $(BIN_DIR)/pdf-concat
+
+test:
+ bats test/
+
A images/README.md => images/README.md +24 -0
@@ 0,0 1,24 @@
+# images utilities
+
+Tools for manipulating image files.
+
+`bats` is required for the test suite.
+
+
+## Specification
+
+These utilities have external dependencies.
+That's an inevitability, given that I will not be writing video codecs.
+
+Executable |Description |Extra Dependencies
+:----------|:---------------------|:-----------------
+pdf-concat |Concatenate PDF files |`bash`,`pdfjam*`
+
+*All* scripts support `-h` and `--help` for printing built-in documentation.
+
+*All* scripts do nothing if no input arguments are given.
+
+\* Support for alternative backends is avaialble.
+Currently this includes `pdfunite`, GhostScript (`gs`), and `pdftk`.
+
+
A images/pdf-concat => images/pdf-concat +149 -0
@@ 0,0 1,149 @@
+#!/bin/bash
+
+name="pdf-concat"
+version="1.0"
+read -r -d '' help_message <<-EOF
+ Concatenate PDF files
+ Usage: pdf-concat [OPTIONS] TARGET [TARGET..]
+ Options:
+ -b BACKEND, select a PDF backend
+ --backend BACKEND
+ -d, --dump print to STDOUT
+ -h, --help print this message
+ -l, --list-backends list available PDF backends
+ -o OUTPUT, write to a file
+ --output OUTPUT
+ -q, --quiet suppress error messages and prompts
+ -v, --verbose show additional messages
+ -V, --version print version number and exit
+EOF
+
+source /usr/local/lib/mylib.bash
+
+select_backend() {
+ if command -v pdfjam >/dev/null 2>&1; then
+ /usr/bin/printf "pdfjam\n"
+ fi
+ if command -v pdfunite >/dev/null 2>&1; then
+ /usr/bin/printf "pdfunite\n"
+ fi
+ if command -v gs >/dev/null 2>&1; then
+ /usr/bin/printf "gs\n"
+ fi
+ if command -v pdftk >/dev/null 2>&1; then
+ /usr/bin/printf "pdftk\n"
+ fi
+}
+
+list_backends() {
+ if command -v pdfjam >/dev/null 2>&1; then
+ msg "pdfjam Interface to the pdfpages package for LaTeX"
+ fi
+ if command -v pdfunite >/dev/null 2>&1; then
+ msg "pdfunite Utility built on FreeDesktop's Poppler library"
+ fi
+ if command -v gs >/dev/null 2>&1; then
+ msg "gs PostScript interpreter"
+ fi
+ if command -v pdftk >/dev/null 2>&1; then
+ msg "pdftk General-purpose PDF toolkit"
+ fi
+}
+
+positional=()
+quiet=0
+verbose=0
+dump=0
+backend="$(select_backend)"
+outfile=""
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+
+ -b|--backend)
+ debug_msg "Setting backend option to ${2} (was ${backend})"
+ dump="$2"
+ shift; shift
+ ;;
+
+ -d|--dump)
+ debug_msg "Setting dump option to 1 (was ${dump})"
+ dump=1
+ shift
+ ;;
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -l|--list-backends)
+ list_backends
+ shift
+ ;;
+
+ -o|--output)
+ debug_msg "Setting outfile option to ${2} (was ${outfile})"
+ outfile="$2"
+ shift; shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting quiet option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting verbose option to 1 (was ${verbose})"
+ verbose=1
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ *)
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# sanitize infiles
+infiles=()
+infiles_count=0
+for target in "${positional[@]}"; do
+ if [[ ! -f "$target" ]]; then
+ error_msg "No such file '${target}'"
+ else
+ infiles+=("$target")
+ infiles_count=1
+ fi
+done
+if [[ "$infiles_count" -eq 0 ]]; then
+ usage_msg
+fi
+infiles_string="$(/usr/bin/printf "%q " "${infiles[@]}")"
+
+# sanitize outfile
+if [[ -f "$outfile" ]]; then
+ if ! prompt_overwrite "$outfile"; then
+ error_msg "No valid output"
+ fi
+fi
+
+# dispatch to backend
+if [[ "$backend" -eq "pdfjam" ]]; then
+ pdfjam -o "$outfile" $infiles_string
+elif [[ "$backend" -eq "pdfunite" ]]; then
+ pdfunite $infiles_string "$outfile"
+elif [[ "$backend" -eq "gs" ]]; then
+ gs -dNOPAUSE -dQUIET -dBATCH -sDEVICE=pdfwrite -sOutputFile="$outfile" $infiles_string
+elif [[ "$backend" -eq "pdftk" ]]; then
+ pdftk $infiles_string cat output "$outfile"
+else
+ error_msg "No valid backend"
+fi
+
A images/test/pdf-concat.bats => images/test/pdf-concat.bats +180 -0
@@ 0,0 1,180 @@
+#!/usr/bin/env bats
+bats_require_minimum_version 1.5.0
+
+@test "pdf-concat usage" {
+ run --separate-stderr pdf-concat
+ [ "$status" -eq 1 ]
+ [ "$output" = "" ]
+ [ "$stderr" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
+}
+
+@test "pdf-concat usage - quiet" {
+ run --separate-stderr pdf-concat --quiet
+ [ "$status" -eq 1 ]
+ [ "$output" = "" ]
+ [ "$stderr" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
+}
+
+@test "pdf-concat usage - quiet short" {
+ run --separate-stderr pdf-concat -q
+ [ "$status" -eq 1 ]
+ [ "$output" = "" ]
+ [ "$stderr" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
+}
+
+@test "pdf-concat version" {
+ run --separate-stderr pdf-concat --version
+ [ "$status" -eq 0 ]
+ [ "$output" = "pdf-concat 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat version - quiet" {
+ run --separate-stderr pdf-concat --version --quiet
+ [ "$status" -eq 0 ]
+ [ "$output" = "pdf-concat 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat version - quiet short" {
+ run --separate-stderr pdf-concat --version -q
+ [ "$status" -eq 0 ]
+ [ "$output" = "pdf-concat 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat version short" {
+ run --separate-stderr pdf-concat -V
+ [ "$status" -eq 0 ]
+ [ "$output" = "pdf-concat 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat version short - quiet" {
+ run --separate-stderr pdf-concat -V --quiet
+ [ "$status" -eq 0 ]
+ [ "$output" = "pdf-concat 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat version short - quiet short" {
+ run --separate-stderr pdf-concat -V --quiet
+ [ "$status" -eq 0 ]
+ [ "$output" = "pdf-concat 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat help" {
+ run --separate-stderr pdf-concat --help
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Concatenate PDF files" ]
+ [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -b BACKEND, select a PDF backend" ]
+ [ "${lines[4]}" = " --backend BACKEND" ]
+ [ "${lines[5]}" = " -d, --dump print to STDOUT" ]
+ [ "${lines[6]}" = " -h, --help print this message" ]
+ [ "${lines[7]}" = " -l, --list-backends list available PDF backends" ]
+ [ "${lines[8]}" = " -o OUTPUT, write to a file" ]
+ [ "${lines[9]}" = " --output OUTPUT" ]
+ [ "${lines[10]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[11]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[12]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat help - quiet" {
+ run --separate-stderr pdf-concat --help --quiet
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Concatenate PDF files" ]
+ [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -b BACKEND, select a PDF backend" ]
+ [ "${lines[4]}" = " --backend BACKEND" ]
+ [ "${lines[5]}" = " -d, --dump print to STDOUT" ]
+ [ "${lines[6]}" = " -h, --help print this message" ]
+ [ "${lines[7]}" = " -l, --list-backends list available PDF backends" ]
+ [ "${lines[8]}" = " -o OUTPUT, write to a file" ]
+ [ "${lines[9]}" = " --output OUTPUT" ]
+ [ "${lines[10]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[11]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[12]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat help - quiet short" {
+ run --separate-stderr pdf-concat --help -q
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Concatenate PDF files" ]
+ [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -b BACKEND, select a PDF backend" ]
+ [ "${lines[4]}" = " --backend BACKEND" ]
+ [ "${lines[5]}" = " -d, --dump print to STDOUT" ]
+ [ "${lines[6]}" = " -h, --help print this message" ]
+ [ "${lines[7]}" = " -l, --list-backends list available PDF backends" ]
+ [ "${lines[8]}" = " -o OUTPUT, write to a file" ]
+ [ "${lines[9]}" = " --output OUTPUT" ]
+ [ "${lines[10]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[11]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[12]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat help short" {
+ run --separate-stderr pdf-concat -h
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Concatenate PDF files" ]
+ [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -b BACKEND, select a PDF backend" ]
+ [ "${lines[4]}" = " --backend BACKEND" ]
+ [ "${lines[5]}" = " -d, --dump print to STDOUT" ]
+ [ "${lines[6]}" = " -h, --help print this message" ]
+ [ "${lines[7]}" = " -l, --list-backends list available PDF backends" ]
+ [ "${lines[8]}" = " -o OUTPUT, write to a file" ]
+ [ "${lines[9]}" = " --output OUTPUT" ]
+ [ "${lines[10]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[11]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[12]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat help short - quiet" {
+ run --separate-stderr pdf-concat -h --quiet
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Concatenate PDF files" ]
+ [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -b BACKEND, select a PDF backend" ]
+ [ "${lines[4]}" = " --backend BACKEND" ]
+ [ "${lines[5]}" = " -d, --dump print to STDOUT" ]
+ [ "${lines[6]}" = " -h, --help print this message" ]
+ [ "${lines[7]}" = " -l, --list-backends list available PDF backends" ]
+ [ "${lines[8]}" = " -o OUTPUT, write to a file" ]
+ [ "${lines[9]}" = " --output OUTPUT" ]
+ [ "${lines[10]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[11]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[12]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "pdf-concat help short - quiet short" {
+ run --separate-stderr pdf-concat --help -q
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Concatenate PDF files" ]
+ [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -b BACKEND, select a PDF backend" ]
+ [ "${lines[4]}" = " --backend BACKEND" ]
+ [ "${lines[5]}" = " -d, --dump print to STDOUT" ]
+ [ "${lines[6]}" = " -h, --help print this message" ]
+ [ "${lines[7]}" = " -l, --list-backends list available PDF backends" ]
+ [ "${lines[8]}" = " -o OUTPUT, write to a file" ]
+ [ "${lines[9]}" = " --output OUTPUT" ]
+ [ "${lines[10]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[11]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[12]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
A videos/Makefile => videos/Makefile +15 -0
@@ 0,0 1,15 @@
+BIN_DIR?=/usr/local/bin
+LIB_DIR?=/usr/local/lib
+COMP_DIR?=/usr/local/etc/bash_completion.d
+
+.PHONY: install uninstall test clean
+
+install:
+ install -m755 thumbnail $(BIN_DIR)/thumbnail
+
+uninstall:
+ rm $(BIN_DIR)/thumbnail
+
+test:
+ bats test/
+
A videos/README.md => videos/README.md +21 -0
@@ 0,0 1,21 @@
+# videos utilities
+
+Tools for manipulating video files.
+
+`bats` is required for the test suite.
+
+
+## Specification
+
+These utilities have external dependencies.
+That's an inevitability, given that I will not be writing video codecs.
+
+Executable |Description |Extra Dependencies
+:---------------|:--------------------------------|:-----------------
+thumbnail |Extract a thumbnail from a video |`bash`, `ffmpeg`
+
+*All* scripts support `-h` and `--help` for printing built-in documentation.
+
+*All* scripts do nothing if no input arguments are given.
+
+
A videos/test/thumbnail.bats => videos/test/thumbnail.bats +151 -0
@@ 0,0 1,151 @@
+#!/usr/bin/env bats
+bats_require_minimum_version 1.5.0
+
+@test "thumbnail usage" {
+ run --separate-stderr thumbnail
+ [ "$status" -eq 1 ]
+ [ "$output" = "" ]
+ [ "$stderr" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
+}
+
+@test "thumbnail usage - quiet" {
+ run --separate-stderr thumbnail --quiet
+ [ "$status" -eq 1 ]
+ [ "$output" = "" ]
+ [ "$stderr" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
+}
+
+@test "thumbnail usage - quiet short" {
+ run --separate-stderr thumbnail -q
+ [ "$status" -eq 1 ]
+ [ "$output" = "" ]
+ [ "$stderr" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
+}
+
+@test "thumbnail version" {
+ run --separate-stderr thumbnail --version
+ [ "$status" -eq 0 ]
+ [ "$output" = "thumbnail 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail version - quiet" {
+ run --separate-stderr thumbnail --version --quiet
+ [ "$status" -eq 0 ]
+ [ "$output" = "thumbnail 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail version - quiet short" {
+ run --separate-stderr thumbnail --version -q
+ [ "$status" -eq 0 ]
+ [ "$output" = "thumbnail 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail version short" {
+ run --separate-stderr thumbnail -V
+ [ "$status" -eq 0 ]
+ [ "$output" = "thumbnail 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail version short - quiet" {
+ run --separate-stderr thumbnail -V --quiet
+ [ "$status" -eq 0 ]
+ [ "$output" = "thumbnail 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail version short - quiet short" {
+ run --separate-stderr thumbnail -V --quiet
+ [ "$status" -eq 0 ]
+ [ "$output" = "thumbnail 1.0" ]
+ [ "$stderr" = "" ]
+}
+
+
+@test "thumbnail help" {
+ run --separate-stderr thumbnail --help
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Extract a thumbnail from a video" ]
+ [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[5]}" = " -t, --timestamp timestamp to extract thumbnail from" ]
+ [ "${lines[6]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[7]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail help - quiet" {
+ run --separate-stderr thumbnail --help --quiet
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Extract a thumbnail from a video" ]
+ [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[5]}" = " -t, --timestamp timestamp to extract thumbnail from" ]
+ [ "${lines[6]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[7]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail help - quiet short" {
+ run --separate-stderr thumbnail --help -q
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Extract a thumbnail from a video" ]
+ [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[5]}" = " -t, --timestamp timestamp to extract thumbnail from" ]
+ [ "${lines[6]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[7]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail help short" {
+ run --separate-stderr thumbnail -h
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Extract a thumbnail from a video" ]
+ [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[5]}" = " -t, --timestamp timestamp to extract thumbnail from" ]
+ [ "${lines[6]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[7]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail help short - quiet" {
+ run --separate-stderr thumbnail -h --quiet
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Extract a thumbnail from a video" ]
+ [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[5]}" = " -t, --timestamp timestamp to extract thumbnail from" ]
+ [ "${lines[6]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[7]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
+@test "thumbnail help short - quiet short" {
+ run --separate-stderr thumbnail --help -q
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Extract a thumbnail from a video" ]
+ [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
+ [ "${lines[2]}" = "Options:" ]
+ [ "${lines[3]}" = " -h, --help print this message" ]
+ [ "${lines[4]}" = " -q, --quiet suppress error messages and prompts" ]
+ [ "${lines[5]}" = " -t, --timestamp timestamp to extract thumbnail from" ]
+ [ "${lines[6]}" = " -v, --verbose show additional messages" ]
+ [ "${lines[7]}" = " -V, --version print version number and exit" ]
+ [ "$stderr" = "" ]
+}
+
A videos/thumbnail => videos/thumbnail +86 -0
@@ 0,0 1,86 @@
+#!/bin/bash
+
+name="thumbnail"
+version="1.0"
+read -r -d '' help_message <<-EOF
+ Extract a thumbnail from a video
+ Usage: thumbnail [OPTIONS] TARGET [TARGET..]
+ Options:
+ -h, --help print this message
+ -q, --quiet suppress error messages and prompts
+ -t, --timestamp timestamp to extract thumbnail from
+ -v, --verbose show additional messages
+ -V, --version print version number and exit
+EOF
+
+source /usr/local/lib/mylib.bash
+
+positional=()
+quiet=
+timestamp="00:00:10.000"
+verbose=
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting quiet option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -t|--time|--timestamp)
+ debug_msg "Setting timestamp option to '${2}' (was '${timestamp}')"
+ timestamp="$2"
+ shift; shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting verbose option to 1 (was ${verbose})"
+ verbose=1
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ *)
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ shift
+ ;;
+ esac
+done
+
+if [[ "${#positional[@]}" -eq 0 ]]; then
+ usage_msg
+fi
+
+printf "$timestamp" | grep -e '^[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]$' >/dev/null 2>&1
+if [[ $? -ne 0 ]]; then
+ error_msg "Not a valid timestamp ('${timestamp}') - must be 'HH:MM:SS.sss'"
+fi
+
+code=0
+for filename in "${positional[@]}"; do
+ if [[ ! -f "$filename" ]]; then
+ nonfatal_error_msg "No such file '$filename'"
+ code=1
+ elif [[ "$filename" = *.png ]]; then
+ nonfatal_error_msg "Skipping file '$filename' - already an image"
+ else
+ dirname="$(dirname "$filename")"
+ basename="$(basename "$filename")"
+ basestub="${basename%.*}"
+ debug_msg "ffmpeg -i '${basename}' -hide_banner -loglevel error -ss '${timestamp}' -frames:v 1 -update 1 '${basestub}.png'"
+ (cd "$dirname" && ffmpeg -i "$basename" -hide_banner -loglevel error -ss "$timestamp" -frames:v 1 -update 1 "$basestub.png")
+ fi
+done
+
+exit "$code"
+