用 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

只是無法利用桌面環境的快捷鍵了。

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