用 Bash 脚本写一个截屏工具

在 Bash 看来,或者在任意一种在 Linux 环境里称得上 Shell 的物种看来,只要有了 ffmpeg、grep、sed、awk 以及一个 X 窗口系统之类的东西,就可以用不到 50 行代码写出一个不错的截屏工具。当然,倘若还有 GIMP 或类似的东西,风味更盛。

时间的名义

知道下面这条命令,在我敲了回车键之后,会输出什么吗?

$ date +"%Y-%m-%d %T"

会输出

2021-02-19 23:32:18

若以这样的结果作为截屏所得图像的文件名,是不是很好?反正我是打算这样做,先将时间里的 : 变成短横:

$ date +"%Y-%m-%d %T" | sed -e "s/ /-/g; s/:/-/g"

时间在流逝:

2021-02-19-23-34-47

将上述代码整合起来,利用子 Shell,就有了截屏脚本 screenshot 的第一步……师出有名:

#!/bin/bash
NAME=$(date +"%Y-%m-%d %T" | sed -e "s/ /-/g; s/:/-/g")
IMAGE=/tmp/${NAME}.png
echo $IMAGE

为 screenshot 添加可执行权限:

$ chmod +x screenshot

然后将其放到系统 PATH 变量所指定的目录内,执行这个脚本,就可以得到截屏结果的文件名:

$ screenshot
/tmp/2021-02-19-23-40-11.png

时间在流逝……

世界有多大?

世界有多大呢……单就截屏而言,世界就是计算机屏幕分辨率那么大。

X 窗口系统有个工具叫 xrandr,它会告诉我世界有多大:

$ xrandr
Screen 0: minimum 8 x 8, current 1600 x 900, maximum 32767 x 32767
LVDS1 connected primary 1600x900+0+0 (normal left inverted right x axis y axis) 310mm x 170mm
 1600x900 60.01*+ 59.82 40.00 
 1400x900 59.96 59.88 
 1368x768 60.00 59.88 59.85 
 1280x800 59.81 59.91 
 1280x720 59.86 60.00 59.74 
 1024x768 60.00 
 1024x576 60.00 59.90 59.82 
 960x540 60.00 59.63 59.82 
 800x600 60.32 56.25 
 864x486 60.00 59.92 59.57 
 800x450 60.00 
 640x480 59.94 
 720x405 59.51 60.00 58.99 
 640x360 59.84 59.32 60.00 
VGA1 disconnected (normal left inverted right x axis y axis)
VIRTUAL1 disconnected (normal left inverted right x axis y axis)

然而,我觉得它的废话太多了。它所说的,只有第一句是我想知道的:

Screen 0: minimum 8 x 8, current 1600 x 900, maximum 32767 x 32767

然而,这一句还是太多了,只有 1600 x 900 是我想知道的……世界就这么大,那就去掉 xrandr 的那些废话,只要把它们扔到管道里,传给 grep,再传给 sed,

$ xrandr | grep -o "current [0-9]* x [0-9]*" | sed -e 's/current *//g'
1600 x 900

用变量记录这个世界的大小,

SCREEN=$(xrandr | grep -o "current [0-9]* x [0-9]*" | sed -e 's/current *//g')
SCREEN_W=$(echo $SCREEN | sed -e 's/ x [0-9]*//')
SCREEN_H=$(echo $SCREEN | sed -e 's/[0-9]* x //')

总会用得着。

窗口

在屏幕上关闭一个窗口的时候,有时会想起一句古老的诗,人生天地间,忽如远行客。我要写的截屏工具,也许能留住任一窗口璀璨的瞬间,只要我知道它在哪里。

一个窗口在哪里,是由它的左上角坐标以及它的宽度和高度决定的。在我还没发现 X 窗口系统的 xwininfo 这个工具之前,我只能考虑使用诗歌来实现这样的截屏工具……

xwininfo 说,我知道你想要知道的,只要你用鼠标点一下你想点的……

$ xwininfo
xwininfo: Please select the window about which you
 would like information by clicking the
 mouse in that window.

我用鼠标左键随便点了一个窗口,xwininfo 把它知道的一切都告诉了我,

xwininfo: Window id: 0x30168e6 "Terminal"
 Absolute upper-left X: 479
 Absolute upper-left Y: 235
 Relative upper-left X: 11
 Relative upper-left Y: 39
 Width: 642
 Height: 434
 Depth: 32
 Visual: 0x104
 Visual Class: TrueColor
 Border width: 0
 Class: InputOutput
 Colormap: 0x3000006 (not installed)
 Bit Gravity State: NorthWestGravity
 Window Gravity State: NorthWestGravity
 Backing Store State: NotUseful
 Save Under State: no
 Map State: IsViewable
 Override Redirect State: no
 Corners: +479+235 -479+235 -479-231 +479-231
 -geometry 80x24+468+196

我想知道的是这些:

 Absolute upper-left X: 479
 Absolute upper-left Y: 235
 Relative upper-left X: 11
 Relative upper-left Y: 39
 Width: 642
 Height: 434

我要把这些数字都提炼出来,放到一个数组里:

declare -a WIN_PARAMS
WIN_PARAMS=($(xwininfo | sed -n -e '/^[[:space:]]*Absolute ..*[XY]/p; 
 /^[[:space:]]*Relative ..*[XY]/p; 
 /^[[:space:]]*Width:/p; 
 /^[[:space:]]*Height:/p' | awk 'BEGIN{FS=":"}{print $2}'))

如果放心不下,想亲自视察 WIN_PARAMS,那就遍历一下它:

for ((i = 0; i < ${#WIN_PARAMS[@]}; i++))
do
 echo ${WIN_PARAMS[$i]}
done

for i in ${WIN_PARAMS[@]}
do
 echo $i
done

相框

没什么好说的,都是小学数学:

# 构造理想中的截图区。
# 之所以如此,与 xwininfo 输出的窗口左上角相对坐标有关。
MARGIN=${WIN_PARAMS[2]}
WIN_X=$((${WIN_PARAMS[0]} - ${WIN_PARAMS[2]}))
WIN_Y=$((${WIN_PARAMS[1]} - ${WIN_PARAMS[3]}))
WIN_W=$((${WIN_PARAMS[4]} + ${WIN_PARAMS[2]} + $MARGIN))
WIN_H=$((${WIN_PARAMS[5]} + ${WIN_PARAMS[3]} + $MARGIN))
# 截图区越界处理
if (($WIN_X < 0)); then WIN_X=0; fi
if (($WIN_Y < 0)); then WIN_Y=0; fi
if (($WIN_W + $WIN_X > $SCREEN_W)); then WIN_W=$(($SCREEN_W - $WIN_X)); fi
if (($WIN_H + $WIN_Y > $SCREEN_H)); then WIN_H=$(($SCREEN_H - $WIN_Y)); fi

截屏

不懂 ffmpeg,也没关系啊……这个截屏工具的精髓,不在这里。

ffmpeg -video_size ${WIN_W}x${WIN_H} \
 -f x11grab -ss 00:00:00 \
 -i :0.0+${WIN_X},${WIN_Y} \
 -frames:v 1 $IMAGE 2>/dev/null

查看/编辑

GIMP 还是挺好用的,虽然在挣钱方面远不如 PhotoShop……

gimp $IMAGE &

示例

完整的 screenshot 脚本……我抓个图吧!

将这个脚本绑定到 Linux 桌面环境里的某个快捷键,应该不难……

后记

screenshot 脚本稍加改造,就可以是一个不错的屏幕录制工具:

#!/bin/bash
NAME=$(date +"%Y-%m-%d %T" | sed -e "s/ /-/g; s/:/-/g")
RECORD=/tmp/${NAME}.mkv
OUTPUT=/tmp/output-${NAME}.mkv
# 获取屏幕分辨率
SCREEN=$(xrandr | grep -o "current [0-9]* x [0-9]*" | sed -e 's/current *//g')
SCREEN_W=$(echo $SCREEN | sed -e 's/ x [0-9]*//')
SCREEN_H=$(echo $SCREEN | sed -e 's/[0-9]* x //')
# 获取窗口几何参数
declare -a WIN_PARAMS
WIN_PARAMS=($(xwininfo | sed -n -e '/^[[:space:]]*Absolute ..*[XY]/p; 
 /^[[:space:]]*Relative ..*[XY]/p; 
 /^[[:space:]]*Width:/p; 
 /^[[:space:]]*Height:/p' | awk 'BEGIN{FS=":"}{print $2}'))
# 构造理想中的截图区。
# 之所以如此,与 xwininfo 输出的窗口左上角相对坐标有关。
MARGIN=${WIN_PARAMS[2]}
WIN_X=$((${WIN_PARAMS[0]} - ${WIN_PARAMS[2]}))
WIN_Y=$((${WIN_PARAMS[1]} - ${WIN_PARAMS[3]}))
WIN_W=$((${WIN_PARAMS[4]} + ${WIN_PARAMS[2]} + $MARGIN))
WIN_H=$((${WIN_PARAMS[5]} + ${WIN_PARAMS[3]} + $MARGIN))
# 截图区越界处理
if (($WIN_X < 0)); then WIN_X=0; fi
if (($WIN_Y < 0)); then WIN_Y=0; fi
if (($WIN_W + $WIN_X > $SCREEN_W)); then WIN_W=$(($SCREEN_W - $WIN_X)); fi
if (($WIN_H + $WIN_Y > $SCREEN_H)); then WIN_H=$(($SCREEN_H - $WIN_Y)); fi
# 录制指定窗口区域
ffmpeg -video_size ${WIN_W}x${WIN_H} \
 -framerate 25 -f x11grab \
 -i :0.0+${WIN_X},${WIN_Y} \
 -c:v libx264rgb -crf 0 -preset ultrafast $RECORD
# 视频后处理
ffmpeg -i $RECORD -c:v libx264rgb -crf 0 -preset veryslow $OUTPUT

只是无法利用桌面环境的快捷键了。要终止屏幕录制过程,需要在运行上述脚本的命令行窗口里摁 q 键。

    作者:garfileo原文地址:https://segmentfault.com/a/1190000039239040

    %s 个评论

    要回复文章请先登录注册