Created
January 14, 2026 20:02
-
-
Save nixjdm/9695b06c156119041e49bd8b2b1c5d20 to your computer and use it in GitHub Desktop.
A simple graphical region selector feeding into ffmpeg video recording
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 | |
| set -euo pipefail | |
| FPS=30 | |
| CQ=23 | |
| PRESET="p5" | |
| AUDIO=0 | |
| USE_LAST=0 | |
| CLEAR_LAST=0 | |
| STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/record-region" | |
| STATE_FILE="$STATE_DIR/last_region.env" | |
| usage() { | |
| cat <<EOF | |
| Usage: | |
| record-region [options] | |
| Options: | |
| --last Use the last saved region (skip selection UI) | |
| --clear Clear the saved region | |
| --audio Record microphone audio (PulseAudio default input) | |
| --fps N Set framerate (default: ${FPS}) | |
| --cq N NVENC constant quality (default: ${CQ}) | |
| -h, --help Show this help | |
| Notes: | |
| - Stop recording with Ctrl+C. | |
| - Output files go to ~/Videos/ | |
| EOF | |
| } | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --last) USE_LAST=1; shift ;; | |
| --clear) CLEAR_LAST=1; shift ;; | |
| --audio) AUDIO=1; shift ;; | |
| --fps) FPS="${2:?missing value}"; shift 2 ;; | |
| --cq) CQ="${2:?missing value}"; shift 2 ;; | |
| -h|--help) usage; exit 0 ;; | |
| *) echo "Unknown option: $1"; usage; exit 1 ;; | |
| esac | |
| done | |
| mkdir -p "$STATE_DIR" | |
| if [[ "$CLEAR_LAST" -eq 1 ]]; then | |
| rm -f "$STATE_FILE" | |
| echo "Cleared saved region." | |
| exit 0 | |
| fi | |
| select_region() { | |
| # This prints shell assignments like: X=.. Y=.. W=.. H=.. | |
| eval "$(slop -f "X=%x Y=%y W=%w H=%h")" | |
| } | |
| load_last_region() { | |
| if [[ ! -f "$STATE_FILE" ]]; then | |
| echo "No saved region found. Run without --last to select one first." | |
| exit 1 | |
| fi | |
| # shellcheck disable=SC1090 | |
| source "$STATE_FILE" | |
| } | |
| save_region() { | |
| cat > "$STATE_FILE" <<EOF | |
| X=${X} | |
| Y=${Y} | |
| W=${W} | |
| H=${H} | |
| EOF | |
| } | |
| # Get region | |
| if [[ "$USE_LAST" -eq 1 ]]; then | |
| load_last_region | |
| else | |
| echo "Drag to select recording region..." | |
| select_region | |
| save_region | |
| fi | |
| # Basic validation | |
| if [[ -z "${W:-}" || -z "${H:-}" || "$W" -le 0 || "$H" -le 0 ]]; then | |
| echo "Invalid region selected." | |
| exit 1 | |
| fi | |
| OUT="$HOME/Videos/Screencasts/region_$(date +%F_%H-%M-%S).mp4" | |
| echo "Recording region: ${W}x${H} at +${X},${Y}" | |
| echo "Saving to: $OUT" | |
| echo "Press Ctrl+C to stop." | |
| # Build ffmpeg command | |
| # crop filter ensures even dimensions for h264 encoding | |
| VIDEO_INPUT=(-f x11grab -framerate "$FPS" -video_size "${W}x${H}" -i "${DISPLAY}+${X},${Y}") | |
| VIDEO_ENC=(-vf "crop=floor(iw/2)*2:floor(ih/2)*2" -c:v h264_nvenc -preset "$PRESET" -cq "$CQ") | |
| if [[ "$AUDIO" -eq 1 ]]; then | |
| ffmpeg \ | |
| "${VIDEO_INPUT[@]}" \ | |
| -f pulse -i default \ | |
| "${VIDEO_ENC[@]}" \ | |
| -c:a aac -b:a 160k \ | |
| "$OUT" | |
| else | |
| ffmpeg \ | |
| "${VIDEO_INPUT[@]}" \ | |
| "${VIDEO_ENC[@]}" \ | |
| "$OUT" | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment