#!/bin/env bash # Script name SCRIPT_NAME=$(basename "$0") # Function to display usage usage() { cat < [options] Commands: screenshot Take a screenshot Targets: selection - Screenshot selected area eDP-1 - Screenshot eDP-1 display HDMI-A-1 - Screenshot HDMI-A-1 display both - Screenshot both displays record Start/stop recording (with audio) Targets: selection - Record selected area eDP-1 - Record eDP-1 display HDMI-A-1 - Record HDMI-A-1 display stop - Stop current recording record-noaudio Start/stop recording (no audio) Targets: selection - Record selected area without audio eDP-1 - Record eDP-1 display without audio HDMI-A-1 - Record HDMI-A-1 display without audio stop - Stop current recording record-hq Start/stop high-quality recording (for YouTube) Targets: selection - Record selected area in high quality eDP-1 - Record eDP-1 display in high quality HDMI-A-1 - Record HDMI-A-1 display in high quality stop - Stop current recording record-gif Start/stop GIF recording Targets: selection - Record selected area as GIF eDP-1 - Record eDP-1 display as GIF HDMI-A-1 - Record HDMI-A-1 display as GIF stop - Stop current recording status Check if recording is active (exit 0 if recording, 1 if not) convert [file] Convert recordings Formats: webm - Convert latest MKV to WebM (or specify file) iphone - Convert latest MKV to iPhone format (or specify file) youtube - Convert latest video to YouTube format (or specify file) gif - Convert latest video to GIF (or specify file) Optional [file] parameter: - If not provided, converts the latest recorded video - If provided, converts the specified file (full path or filename in Recordings folder) Examples: $SCRIPT_NAME screenshot selection $SCRIPT_NAME record eDP-1 $SCRIPT_NAME record-noaudio selection $SCRIPT_NAME record-hq eDP-1 $SCRIPT_NAME record-gif selection $SCRIPT_NAME record stop $SCRIPT_NAME convert gif # Convert latest video to GIF $SCRIPT_NAME convert youtube # Convert latest video for YouTube $SCRIPT_NAME convert webm /path/to/video.mkv # Convert specific file to WebM EOF exit 1 } # Check if no arguments provided if [ $# -eq 0 ]; then usage fi # Function to send screenshot notification with action buttons send_screenshot_notification() { local full_path="$1" local save_dir=$(dirname "$full_path") ACTION=$(notify-send -a "Modus" -i "$full_path" "Screenshot saved" "in $full_path" \ -A "view=View" -A "edit=Edit" -A "open=Open Folder") case "$ACTION" in view) xdg-open "$full_path" ;; edit) swappy -f "$full_path" ;; open) xdg-open "$save_dir" ;; esac } # Function to send recording notification with action buttons send_recording_notification() { local full_path="$1" local save_dir=$(dirname "$full_path") ACTION=$(notify-send -a "Modus" -i "camera-video-symbolic" "Recording saved" "in $full_path" \ -A "view=View" -A "open=Open Folder") case "$ACTION" in view) xdg-open "$full_path" ;; open) xdg-open "$save_dir" ;; esac } wf-recorder_check() { if pgrep -x "wf-recorder" >/dev/null; then pkill -INT -x wf-recorder # Get the recording file path and send notification with actions if [ -f /tmp/recording.txt ]; then recording_file=$(cat /tmp/recording.txt) wl-copy <"$recording_file" send_recording_notification "$recording_file" else notify-send "Recording stopped" "wf-recorder process terminated" fi # Clean up recording start time file rm -f /tmp/recording_start_time.txt exit 0 fi } # Function to record with standard settings record_video() { local output_file="$1" shift wf-recorder "$@" -f "$output_file" -c libvpx-vp9 --pixel-format yuv420p -F "eq=brightness=0.12:contrast=1.1" } # Function to record without audio record_video_noaudio() { local output_file="$1" shift wf-recorder "$@" -f "$output_file" -c libvpx-vp9 --pixel-format yuv420p -F "eq=brightness=0.12:contrast=1.1" --no-audio } record_high_quality() { local output_file="$1" shift # High quality settings for YouTube uploads # - h264_vaapi for hardware encoding (if available) or libx264 for software # - yuv420p pixel format for maximum compatibility # - High bitrate (8000k) for quality # - GOP size of 30 for better seeking # - Preset 'slow' for better compression efficiency # - CRF 18 for high quality (lower = better quality, 0-51 scale) # - Audio at 192k bitrate # - 60 FPS for smooth motion # - No color filters to maintain original colors # Check if VAAPI hardware encoding is available if vainfo &>/dev/null && wf-recorder --help | grep -q "h264_vaapi"; then # Use hardware encoding for better performance wf-recorder "$@" -f "$output_file" \ -c h264_vaapi \ -p "preset=slow" \ -p "crf=18" \ -r 60 \ -b 8000000 \ -B 192000 \ --pixel-format yuv420p \ -g 30 else # Fallback to software encoding wf-recorder "$@" -f "$output_file" \ -c libx264 \ -p "preset=slow" \ -p "crf=18" \ -r 60 \ -b 8000000 \ -B 192000 \ --pixel-format yuv420p \ -g 30 fi } record_gif() { local output_file="$1" shift # Record temporary video first (MKV format for better quality) local temp_video="/tmp/gif_recording_$(date +%s).mkv" echo "$temp_video" >/tmp/gif_temp_video.txt # GIF-optimized recording settings: # - Lower framerate (15 fps) for smaller file size # - No audio recording # - Standard codec for compatibility wf-recorder "$@" -f "$temp_video" \ -c libvpx-vp9 \ -r 15 \ --pixel-format yuv420p \ --no-audio # After recording stops, convert to GIF if [ -f "$temp_video" ]; then notify-send "Converting to GIF" "Processing recording..." # Create high-quality GIF with optimized palette # Using ffmpeg with palette generation for better colors ffmpeg -i "$temp_video" \ -vf "fps=15,scale=iw:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128:stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" \ -loop 0 \ "$output_file" 2>/tmp/gif_conversion.log if [ $? -eq 0 ]; then # Clean up temp file rm -f "$temp_video" rm -f /tmp/gif_temp_video.txt # Copy to clipboard and send notification with actions wl-copy <"$output_file" send_recording_notification "$output_file" else error=$(cat /tmp/gif_conversion.log | tail -n 5) notify-send "GIF Conversion Failed" "Error: $error" rm -f "$temp_video" rm -f /tmp/gif_temp_video.txt fi fi } # Function to find the latest video file for conversion find_latest_video() { local format="$1" local recording_dir="${HOME}/Videos/Recordings" case "$format" in "webm"|"iphone") # For webm and iphone, only look for MKV files find "$recording_dir" -name "*.mkv" -type f -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -1 | cut -d' ' -f2- ;; "youtube"|"gif") # For youtube and gif, look for both MKV and MP4 files find "$recording_dir" \( -name "*.mkv" -o -name "*.mp4" \) -type f -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -1 | cut -d' ' -f2- ;; *) echo "" ;; esac } # Function to resolve video file path resolve_video_file() { local format="$1" local file_param="$2" local recording_dir="${HOME}/Videos/Recordings" if [ -n "$file_param" ]; then # File parameter provided if [ -f "$file_param" ]; then # Full path provided and exists echo "$file_param" elif [ -f "$recording_dir/$file_param" ]; then # Filename provided, exists in recordings folder echo "$recording_dir/$file_param" else echo "" fi else # No file parameter, find latest find_latest_video "$format" fi } # Parse command COMMAND="$1" TARGET="$2" FILE_PARAM="$3" # Optional file parameter for convert command # Set up file paths IMG="${HOME}/Pictures/Screenshots/$(date +%Y-%m-%d_%H-%m-%s).png" VID="${HOME}/Videos/Recordings/$(date +%Y-%m-%d_%H-%m-%s).mkv" case "$COMMAND" in "screenshot") case "$TARGET" in "selection") grim -g "$(slurp)" "$IMG" wl-copy <"$IMG" send_screenshot_notification "$IMG" ;; "eDP-1") grim -c -o eDP-1 "$IMG" wl-copy <"$IMG" send_screenshot_notification "$IMG" ;; "HDMI-A-1") grim -c -o HDMI-A-1 "$IMG" wl-copy <"$IMG" send_screenshot_notification "$IMG" ;; "both") grim -c -o eDP-1 "${IMG//.png/-eDP-1.png}" grim -c -o HDMI-A-1 "${IMG//.png/-HDMI-A-1.png}" montage "${IMG//.png/-eDP-1.png}" "${IMG//.png/-HDMI-A-1.png}" -tile 2x1 -geometry +0+0 "$IMG" wl-copy <"$IMG" rm "${IMG//.png/-eDP-1.png}" "${IMG//.png/-HDMI-A-1.png}" send_screenshot_notification "$IMG" ;; *) echo "Error: Invalid screenshot target '$TARGET'" usage ;; esac ;; "record") case "$TARGET" in "stop") wf-recorder_check ;; "selection") wf-recorder_check echo "$VID" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt record_video "$VID" -g "$(slurp)" ;; "eDP-1") wf-recorder_check echo "$VID" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt record_video "$VID" -a -o eDP-1 ;; "HDMI-A-1") wf-recorder_check echo "$VID" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt record_video "$VID" -a -o HDMI-A-1 ;; *) echo "Error: Invalid record target '$TARGET'" usage ;; esac ;; "record-noaudio") case "$TARGET" in "stop") wf-recorder_check ;; "selection") wf-recorder_check echo "$VID" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt record_video_noaudio "$VID" -g "$(slurp)" ;; "eDP-1") wf-recorder_check echo "$VID" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt record_video_noaudio "$VID" -o eDP-1 ;; "HDMI-A-1") wf-recorder_check echo "$VID" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt record_video_noaudio "$VID" -o HDMI-A-1 ;; *) echo "Error: Invalid record-noaudio target '$TARGET'" usage ;; esac ;; "record-hq") # Change file extension to mp4 for high quality recordings VID_HQ="${HOME}/Videos/Recordings/$(date +%Y-%m-%d_%H-%m-%s)-hq.mp4" case "$TARGET" in "stop") wf-recorder_check ;; "selection") wf-recorder_check echo "$VID_HQ" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt notify-send "High Quality Recording" "Starting YouTube-quality recording..." record_high_quality "$VID_HQ" -g "$(slurp)" ;; "eDP-1") wf-recorder_check echo "$VID_HQ" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt notify-send "High Quality Recording" "Starting YouTube-quality recording on eDP-1..." record_high_quality "$VID_HQ" -a -o eDP-1 ;; "HDMI-A-1") wf-recorder_check echo "$VID_HQ" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt notify-send "High Quality Recording" "Starting YouTube-quality recording on HDMI-A-1..." record_high_quality "$VID_HQ" -a -o HDMI-A-1 ;; *) echo "Error: Invalid record-hq target '$TARGET'" usage ;; esac ;; "record-gif") # GIF files go to a specific location GIF="${HOME}/Videos/Recordings/$(date +%Y-%m-%d_%H-%m-%s).gif" case "$TARGET" in "stop") wf-recorder_check ;; "selection") wf-recorder_check echo "$GIF" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt notify-send "GIF Recording" "Starting GIF recording (15 FPS)..." record_gif "$GIF" -g "$(slurp)" ;; "eDP-1") wf-recorder_check echo "$GIF" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt notify-send "GIF Recording" "Starting GIF recording on eDP-1..." record_gif "$GIF" -o eDP-1 ;; "HDMI-A-1") wf-recorder_check echo "$GIF" >/tmp/recording.txt date +%s >/tmp/recording_start_time.txt notify-send "GIF Recording" "Starting GIF recording on HDMI-A-1..." record_gif "$GIF" -o HDMI-A-1 ;; *) echo "Error: Invalid record-gif target '$TARGET'" usage ;; esac ;; "status") # Check if wf-recorder is running if pgrep -x "wf-recorder" >/dev/null; then echo "true" exit 0 else echo "false" exit 0 fi ;; "convert") case "$TARGET" in "webm") # Check if ffmpeg is installed if ! command -v ffmpeg >/dev/null 2>&1; then notify-send "Error" "ffmpeg is not installed. Please install it to use this feature." exit 1 fi # Resolve the video file to convert video_file=$(resolve_video_file "webm" "$FILE_PARAM") if [ -z "$video_file" ] || [ ! -f "$video_file" ]; then if [ -n "$FILE_PARAM" ]; then notify-send "WebM Conversion Error" "File not found: $FILE_PARAM" else notify-send "WebM Conversion Error" "No MKV files found in Recordings folder" fi exit 1 fi # Ensure it's an MKV file if [[ "$video_file" != *.mkv ]]; then notify-send "WebM Conversion Error" "Only MKV files can be converted to WebM. Found: $(basename "$video_file")" exit 1 fi webm_file="${video_file%.mkv}.webm" # Check if webm version doesn't already exist if [ -f "$webm_file" ]; then notify-send "WebM Conversion Skipped" "WebM version already exists: $(basename "$webm_file")" exit 0 fi notify-send "Converting to WebM" "Processing: $(basename "$video_file")" # Convert the file ffmpeg -y -i "$video_file" -c:v libvpx -b:v 1M -c:a libvorbis "$webm_file" 2>/tmp/ffmpeg_error.log if [ $? -eq 0 ]; then file_size=$(du -h "$webm_file" | cut -f1) notify-send "WebM Conversion Success" "$(basename "$video_file") → $(basename "$webm_file") ($file_size)" else error=$(cat /tmp/ffmpeg_error.log | tail -n 5) notify-send "WebM Conversion Failed" "Error: $error" fi ;; "iphone") # Check if ffmpeg is installed if ! command -v ffmpeg >/dev/null 2>&1; then notify-send "Error" "ffmpeg is not installed. Please install it to use this feature." exit 1 fi # Resolve the video file to convert video_file=$(resolve_video_file "iphone" "$FILE_PARAM") if [ -z "$video_file" ] || [ ! -f "$video_file" ]; then if [ -n "$FILE_PARAM" ]; then notify-send "iPhone Conversion Error" "File not found: $FILE_PARAM" else notify-send "iPhone Conversion Error" "No MKV files found in Recordings folder" fi exit 1 fi # Ensure it's an MKV file if [[ "$video_file" != *.mkv ]]; then notify-send "iPhone Conversion Error" "Only MKV files can be converted for iPhone. Found: $(basename "$video_file")" exit 1 fi base_filename=$(basename "$video_file") # Skip files with "iphone" in the filename if [[ $base_filename == *"iphone"* ]]; then notify-send "iPhone Conversion Skipped" "File already appears to be iPhone format: $(basename "$video_file")" exit 0 fi iphone_file="${video_file%.mkv}-iphone.mp4" # Check if iPhone version doesn't already exist if [ -f "$iphone_file" ]; then notify-send "iPhone Conversion Skipped" "iPhone version already exists: $(basename "$iphone_file")" exit 0 fi notify-send "Converting for iPhone" "Processing: $(basename "$video_file")" # Convert the file ffmpeg -y -i "$video_file" -vcodec h264 -acodec aac "$iphone_file" 2>/tmp/ffmpeg_error.log if [ $? -eq 0 ]; then file_size=$(du -h "$iphone_file" | cut -f1) notify-send "iPhone Conversion Success" "$(basename "$video_file") → $(basename "$iphone_file") ($file_size)" else error=$(cat /tmp/ffmpeg_error.log | tail -n 5) notify-send "iPhone Conversion Failed" "Error: $error" fi ;; "youtube") # Check if ffmpeg is installed if ! command -v ffmpeg >/dev/null 2>&1; then notify-send "Error" "ffmpeg is not installed. Please install it to use this feature." exit 1 fi # Resolve the video file to convert video_file=$(resolve_video_file "youtube" "$FILE_PARAM") if [ -z "$video_file" ] || [ ! -f "$video_file" ]; then if [ -n "$FILE_PARAM" ]; then notify-send "YouTube Conversion Error" "File not found: $FILE_PARAM" else notify-send "YouTube Conversion Error" "No video files found in Recordings folder" fi exit 1 fi base_filename=$(basename "$video_file") # Skip files already marked as YouTube uploads if [[ $base_filename == *"youtube"* ]]; then notify-send "YouTube Conversion Skipped" "File already appears to be YouTube format: $(basename "$video_file")" exit 0 fi # Create YouTube optimized filename youtube_file="${video_file%.*}-youtube.mp4" # Check if YouTube version doesn't already exist if [ -f "$youtube_file" ]; then notify-send "YouTube Conversion Skipped" "YouTube version already exists: $(basename "$youtube_file")" exit 0 fi notify-send "Converting for YouTube" "Processing: $(basename "$video_file")" # YouTube recommended settings: # - H.264 codec with High profile # - 1080p or source resolution # - 60fps or source framerate # - High bitrate for quality (8-12 Mbps for 1080p60) # - AAC audio at 384kbps # - yuv420p pixel format for compatibility # - Keyframe interval of 2 seconds (GOP) # - No filters to preserve original colors ffmpeg -y -i "$video_file" \ -c:v libx264 \ -profile:v high \ -preset slow \ -crf 18 \ -pix_fmt yuv420p \ -c:a aac \ -b:a 384k \ -movflags +faststart \ "$youtube_file" 2>/tmp/ffmpeg_error.log if [ $? -eq 0 ]; then file_size=$(du -h "$youtube_file" | cut -f1) notify-send "YouTube Conversion Success" "$(basename "$video_file") → $(basename "$youtube_file") ($file_size)" else error=$(cat /tmp/ffmpeg_error.log | tail -n 5) notify-send "YouTube Conversion Failed" "Error converting $(basename "$video_file"): $error" fi ;; "gif") # Check if ffmpeg is installed if ! command -v ffmpeg >/dev/null 2>&1; then notify-send "Error" "ffmpeg is not installed. Please install it to use this feature." exit 1 fi # Resolve the video file to convert video_file=$(resolve_video_file "gif" "$FILE_PARAM") if [ -z "$video_file" ] || [ ! -f "$video_file" ]; then if [ -n "$FILE_PARAM" ]; then notify-send "GIF Conversion Error" "File not found: $FILE_PARAM" else notify-send "GIF Conversion Error" "No video files found in Recordings folder" fi exit 1 fi base_filename=$(basename "$video_file") # Skip files already GIFs if [[ $base_filename == *.gif ]]; then notify-send "GIF Conversion Skipped" "File is already a GIF: $(basename "$video_file")" exit 0 fi # Create GIF filename gif_file="${video_file%.*}.gif" # Check if GIF version doesn't already exist if [ -f "$gif_file" ]; then notify-send "GIF Conversion Skipped" "GIF version already exists: $(basename "$gif_file")" exit 0 fi notify-send "Converting to GIF" "Processing: $(basename "$video_file")" # Get video dimensions for scaling width=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 "$video_file") # Scale down if wider than 800px to keep file size reasonable if [ "$width" -gt 800 ]; then scale_filter="scale=800:-1:flags=lanczos," else scale_filter="" fi # Create high-quality GIF with optimized palette ffmpeg -i "$video_file" \ -vf "${scale_filter}fps=15,split[s0][s1];[s0]palettegen=max_colors=256:stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" \ -loop 0 \ "$gif_file" 2>/tmp/ffmpeg_error.log if [ $? -eq 0 ]; then file_size=$(du -h "$gif_file" | cut -f1) notify-send "GIF Conversion Success" "$(basename "$video_file") → $(basename "$gif_file") ($file_size)" send_recording_notification "$gif_file" else error=$(cat /tmp/ffmpeg_error.log | tail -n 5) notify-send "GIF Conversion Failed" "Error converting $(basename "$video_file"): $error" fi ;; *) echo "Error: Invalid convert format '$TARGET'" usage ;; esac ;; *) echo "Error: Invalid command '$COMMAND'" usage ;; esac