Created
February 24, 2026 10:58
-
-
Save Konfekt/dc7313ca6c8bc15cbba75172fb4fd031 to your computer and use it in GitHub Desktop.
Convert file to PNG using Firefox's headless screenshot feature
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Render a local file (or URL) to PNG using Firefox headless screenshot. | |
| # This is often a high-fidelity rasterizer for SVG/HTML/CSS compared to some converters. | |
| set -o errtrace -o errexit -o pipefail | |
| [[ "${TRACE:-0}" == "1" ]] && set -o xtrace | |
| if [[ "${BASH_VERSINFO:-0}" -gt 4 || ( "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 ) ]]; then | |
| shopt -s inherit_errexit | |
| fi | |
| PS4='+\t ' | |
| [[ ! -t 0 ]] && [[ -n "${DBUS_SESSION_BUS_ADDRESS:-}" ]] && command -v notify-send >/dev/null 2>&1 && notify=1 | |
| error_handler() { | |
| local line="$1" bash_lineno="$2" cmd="$3" status="$4" | |
| local start=$(( line > 3 ? line - 3 : 1 )) | |
| local summary="Error: In ${BASH_SOURCE[0]}, Line ${line}, Command ${cmd} exited with Status ${status}" | |
| local body | |
| body="$(pr -tn "${BASH_SOURCE[0]}" | tail -n+"$start" | head -n7 | sed '4s/^[[:space:]]*/>> /')" | |
| echo >&2 -e "${summary}\n${body}" | |
| [[ -z "${notify:+x}" ]] || notify-send --urgency=critical "$summary" "$body" | |
| exit "$status" | |
| } | |
| trap 'error_handler "$LINENO" "${BASH_LINENO[0]:-?}" "$BASH_COMMAND" "$?"' ERR | |
| usage() { | |
| cat <<'TXT' | |
| firefox-screenshot.sh: Render an input in Firefox headless mode and save a PNG screenshot. | |
| Usage: | |
| firefox-screenshot.sh [-o output.png] [-s width,height] <input> | |
| Arguments: | |
| <input> Local path or URL. | |
| Options: | |
| -o, --output Output PNG path. | |
| -s, --size Window size as "width,height", e.g. "1920,1080". | |
| -h, --help Show help. | |
| Rules: | |
| If <input> is a local path and -o is omitted, derive output as "<input-without-last-extension>.png". | |
| If <input> is a URL, require -o. | |
| TXT | |
| } | |
| abs_path() { | |
| local p="$1" | |
| if command -v realpath >/dev/null 2>&1; then | |
| realpath -- "$p" | |
| elif command -v python3 >/dev/null 2>&1; then | |
| python3 - <<'PY' "$p" | |
| import os,sys | |
| print(os.path.realpath(sys.argv[1])) | |
| PY | |
| else | |
| (cd -- "$(dirname -- "$p")" && printf '%s/%s\n' "$PWD" "$(basename -- "$p")") | |
| fi | |
| } | |
| default_output_for_file() { | |
| local in="$1" base | |
| base="$(basename -- "$in")" | |
| if [[ "$base" == *.* && "$base" != .* ]]; then | |
| printf '%s.png\n' "${in%.*}" | |
| else | |
| printf '%s.png\n' "$in" | |
| fi | |
| } | |
| main() { | |
| command -v firefox >/dev/null 2>&1 || { echo "Firefox is required but not found." >&2; return 1; } | |
| local output="" size="" input="" | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -h|--help) usage; return 0 ;; | |
| -o|--output) output="${2-}"; shift 2 ;; | |
| -s|--size) size="${2-}"; shift 2 ;; | |
| --) shift; break ;; | |
| -*) echo "Unknown option: $1" >&2; usage >&2; return 2 ;; | |
| *) input="$1"; shift; break ;; | |
| esac | |
| done | |
| [[ -n "$input" ]] || { usage >&2; return 2; } | |
| local uri | |
| if [[ "$input" =~ ^[a-zA-Z][a-zA-Z0-9+.-]*:// ]]; then | |
| uri="$input" | |
| [[ -n "$output" ]] || { echo "Output path is required for URL input." >&2; return 2; } | |
| else | |
| [[ -f "$input" ]] || { echo "Input path not found: $input" >&2; return 1; } | |
| uri="file://$(abs_path "$input")" | |
| [[ -n "$output" ]] || output="$(default_output_for_file "$input")" | |
| fi | |
| local dir="${XDG_CACHE_HOME:-$HOME/.cache}" | |
| mkdir -p -- "$dir" | |
| local temp | |
| temp="$(mktemp -d -- "$dir/firefox-XXXXXX")" | |
| trap 'rm -rf -- "$temp"' INT TERM EXIT | |
| local args=(--profile "$temp" --headless --new-instance --screenshot "$output") | |
| [[ -n "$size" ]] && args+=(--window-size "$size") | |
| firefox "${args[@]}" "$uri" | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment