From ef6d4cd1b378cc0ebbcb6e1460b3909f0cdd71b6 Mon Sep 17 00:00:00 2001 From: Dominic Ricottone Date: Fri, 20 Dec 2024 14:54:14 -0600 Subject: [PATCH] Incomplete work on hardware/media utils --- Makefile | 8 ++ core/Makefile | 2 + core/dired | 42 +++++++++ core/git-blobs | 24 +++++ core/test/dired.bats | 138 +++++++++++++++++++++++++++ hardware/gpu-ls | 10 ++ hardware/gpu-ps | 24 +++++ images/Makefile | 15 +++ images/README.md | 24 +++++ images/pdf-concat | 149 +++++++++++++++++++++++++++++ images/test/pdf-concat.bats | 180 ++++++++++++++++++++++++++++++++++++ videos/Makefile | 15 +++ videos/README.md | 21 +++++ videos/test/thumbnail.bats | 151 ++++++++++++++++++++++++++++++ videos/thumbnail | 86 +++++++++++++++++ 15 files changed, 889 insertions(+) create mode 100644 core/dired create mode 100755 core/git-blobs create mode 100644 core/test/dired.bats create mode 100755 hardware/gpu-ls create mode 100755 hardware/gpu-ps create mode 100644 images/Makefile create mode 100644 images/README.md create mode 100755 images/pdf-concat create mode 100644 images/test/pdf-concat.bats create mode 100644 videos/Makefile create mode 100644 videos/README.md create mode 100644 videos/test/thumbnail.bats create mode 100755 videos/thumbnail diff --git a/Makefile b/Makefile index 144294b..8da746e 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/core/Makefile b/core/Makefile index 769c2de..cc2b0a4 100644 --- a/core/Makefile +++ b/core/Makefile @@ -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 diff --git a/core/dired b/core/dired new file mode 100644 index 0000000..2b14736 --- /dev/null +++ b/core/dired @@ -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 + diff --git a/core/git-blobs b/core/git-blobs new file mode 100755 index 0000000..7c7a98a --- /dev/null +++ b/core/git-blobs @@ -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 + diff --git a/core/test/dired.bats b/core/test/dired.bats new file mode 100644 index 0000000..ec28c62 --- /dev/null +++ b/core/test/dired.bats @@ -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" = "" ] +} + diff --git a/hardware/gpu-ls b/hardware/gpu-ls new file mode 100755 index 0000000..4d53fa2 --- /dev/null +++ b/hardware/gpu-ls @@ -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 + diff --git a/hardware/gpu-ps b/hardware/gpu-ps new file mode 100755 index 0000000..e3d5cde --- /dev/null +++ b/hardware/gpu-ps @@ -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 + diff --git a/images/Makefile b/images/Makefile new file mode 100644 index 0000000..b0b08d2 --- /dev/null +++ b/images/Makefile @@ -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/ + diff --git a/images/README.md b/images/README.md new file mode 100644 index 0000000..f52ba16 --- /dev/null +++ b/images/README.md @@ -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`. + + diff --git a/images/pdf-concat b/images/pdf-concat new file mode 100755 index 0000000..7b6f3c1 --- /dev/null +++ b/images/pdf-concat @@ -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 + diff --git a/images/test/pdf-concat.bats b/images/test/pdf-concat.bats new file mode 100644 index 0000000..abfd685 --- /dev/null +++ b/images/test/pdf-concat.bats @@ -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" = "" ] +} + diff --git a/videos/Makefile b/videos/Makefile new file mode 100644 index 0000000..f3ad600 --- /dev/null +++ b/videos/Makefile @@ -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/ + diff --git a/videos/README.md b/videos/README.md new file mode 100644 index 0000000..495ced4 --- /dev/null +++ b/videos/README.md @@ -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. + + diff --git a/videos/test/thumbnail.bats b/videos/test/thumbnail.bats new file mode 100644 index 0000000..a3f602f --- /dev/null +++ b/videos/test/thumbnail.bats @@ -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" = "" ] +} + diff --git a/videos/thumbnail b/videos/thumbnail new file mode 100755 index 0000000..cbb7dfd --- /dev/null +++ b/videos/thumbnail @@ -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" + -- 2.45.2