用 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

相框

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

MARGIN=12
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_X + $WIN_W > $SCREEN_W))
then
    WIN_W=$(($SCREEN_W - $WIN_X))
fi
if (($WIN_Y + $WIN_H > $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}'))

# 构造理想中的截图区
MARGIN=12
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

只是无法利用桌面环境的快捷键了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章