在 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
键。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。