Posts

turn image folder into a single pdf

 #!/usr/bin/env bash # Usage: imagefolder_to_pdf.sh /path/to/folder # Creates folder.pdf from all images (JPG/PNG/etc) in that folder at 150 dpi. set -euo pipefail if [ "${1:-}" = "-h" ] || [ "$#" -lt 1 ]; then   echo "Usage: $(basename "$0") /path/to/folder" >&2   exit 1 fi indir="$1" # Strip trailing slash, then append .pdf foldername="$(basename "${indir%/}")" outfile="$(dirname "$indir")/${foldername}.pdf" if [ ! -d "$indir" ]; then   echo "Error: folder not found: $indir" >&2   exit 2 fi # Gather images mapfile -d '' files < <(   find "$indir" -maxdepth 1 -type f \     \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.tif' -o -iname '*.tiff' -o -iname '*.bmp' -o -iname '*.webp' \) \     -print0 | sort -z -V ) if [ "${#files[@]}" -eq 0 ]; then ...

rotate pdf pages of bitmap scan pdfs any degrees

 #!/bin/bash # usage: pdfrotate.sh <angle> <file.pdf> # Rotates all pages of a PDF using pdftk (for 90° increments) or Ghostscript otherwise. angle=$1 file=$2 if [ -z "$angle" ] || [ -z "$file" ]; then     echo "Usage: $0 <angle> <file.pdf>"     exit 1 fi base="${file%.pdf}" out="${base}_rotated_${angle}.pdf" # Round angle if needed norm_angle=$((angle % 360)) # Handle pdftk for standard rotations if [[ "$norm_angle" == "90" || "$norm_angle" == "180" || "$norm_angle" == "270" || "$norm_angle" == "0" ]]; then     case $norm_angle in         0)   dir="north" ;;   # no rotation         90)  dir="right" ;;         180) dir="down" ;;         270) dir="left" ;;     esac     echo "Rotating with pdftk ($norm_angle°)..."     pdftk "$file" cat 1-end${dir} output "$out" else  ...

transcribe a folder of m4a files to text

 #!/bin/bash # convert_and_transcribe.sh # 1. Rename spaces to underscores for f in *\ *; do mv "$f" "${f// /_}"; done # 2. Convert all .m4a files to .mp3 for f in *.m4a; do ffmpeg -i "$f" -acodec libmp3lame -ab 192k "${f%.m4a}.mp3"; done # 3. Move original .m4a files to Trash mv *.m4a ~/.local/share/Trash/files/ 2>/dev/null # 4. Transcribe all .mp3 files using Whisper for f in *.mp3; do whisper "$f" --model medium --language en --output_format txt --output_dir . --fp16 False; done

get rid of all weird characters in filenames

#!/usr/bin/env bash # rename_youtube_clean.sh for f in *; do   [ -f "$f" ] || continue   case "$f" in *.sh) continue;; esac   new="$(printf '%s' "$f" |     tr -cd '\0-\177' |     sed -E '       s/\([^)]*\)//g;            # drop (...)       s/\[[^]]*\]//g;            # drop [...]       s/[⧸||]/_/g;              # strip slashes/pipes       s/[–—]/-/g;                # normalise dashes       s/  +/ /g;                 # squeeze spaces       s/^ +//; s/ +$//;          # trim       s/ - /-/g;                 # tighten hyphen       s/ /_/g;              ...

Why xdg-open is Irritating by Default (and How to Fix It)

  Why xdg-open is Irritating by Default (and How to Fix It) If you’ve ever tried to open a batch of files on Linux using xdg-open , you’ve probably discovered a big irritation: it doesn’t accept shell globs properly . On macOS, the open command feels natural. You can run: open *.jpg …and every JPEG in your folder pops open in your default viewer. Simple, predictable, done. On Linux, however, xdg-open — the command that sits behind the /usr/bin/open symlink on many systems — behaves differently. While it will happily open a single file, it stumbles when you throw a wildcard at it. Rather than expanding the glob and opening each file, it usually just tries (and fails) on the first one, or misinterprets the argument altogether. That makes it useless in the very situations you’d expect it to shine: folders full of images, PDFs, or text files you want to view in bulk. The Fix: Replace open with a Smarter Wrapper Fortunately, the shell itself can handle globbing. The trick...

renew ssl certs automatically and clear out expired

 Letsencrypt used to auto fix your expired certs. This no longer happens. Here's a script to do it. Flags: --delete : remove expired certs --renew: renew soon-to-expire certs (3< days) #!/bin/bash # check-certs.sh base="/etc/letsencrypt/archive" delete_mode=0 renew_mode=0 if [ "$1" == "--delete" ]; then     delete_mode=1 elif [ "$1" == "--renew" ]; then     renew_mode=1 fi # First: find all current certs in archive declare -A cert_status find "$base" -type f -name "cert*.pem" | while read cert; do     domain=$(echo "$cert" | sed -E "s|$base/([^/]+)/cert[0-9]+\.pem|\1|")     expiry=$(openssl x509 -enddate -noout -in "$cert" | cut -d= -f2)     exp_epoch=$(date -d "$expiry" +%s)     now_epoch=$(date +%s)     days_left=$(( (exp_epoch - now_epoch) / 86400 ))     echo "$domain: $expiry  ($days_left days left)"     if [ $delete_mode -eq 1 ] && [ $days_left -lt...

find deleted files

Sometimes you want to compare an archive backup drive and a target folder and see what items got  deleted in that folder. Useful if you have a flaky backup system like dropbox. #!/usr/bin/env bash # find_deleted.sh — list non-image files that exist in SOURCE but not in TARGET # usage: ./find_deleted.sh [--rsync-check] SOURCE_DIR TARGET_DIR set -euo pipefail usage() {   echo "Usage: $0 [--rsync-check] SOURCE_DIR TARGET_DIR" >&2   exit 1 } RSYNC_CHECK=0 if [ "${1:-}" = "--rsync-check" ]; then   RSYNC_CHECK=1   shift fi [ $# -eq 2 ] || usage SRC="${1%/}" DST="${2%/}" [ -d "$SRC" ] || { echo "Source not found: $SRC" >&2; exit 2; } [ -d "$DST" ] || { echo "Target not found: $DST" >&2; exit 3; } # Returns 0 if the path looks like an image, 1 otherwise. is_image_path() {   # lower-case path (Bash-only ${var,,})   local p="${1,,}"   case "$p" in     *.jpg|*.jpeg|*....