Picture-in-picture timelapse of dual monitors

A few months ago, I wrote a post on how to do a picture-in-picture timelapse recording when dealing with a dual monitor setup where each monitor has its own separate X display.

Long story short, after a harddrive failure forcing me to reconfigure my linux setup, I went away from the dual X screen setup, to a single X display spanning the two montiors.

This means that a screen dump using xwd -root -out screenshot.xwd produces a single image of both monitors side-by-side. This also means the script I wrote for the previous post no longer works. A rewrite was in order to deal with the new setup, and that's what this post is for.

I'll keep this post short with only the script and a demo recording (a timelapse made while writing this post). For an explanation of the ImageMagic magic, see the previous post.

1. Script

Putting together the various pieces mentioned so far, this is the final script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/bin/bash
 
# Setup:
####################
# Automatic setup detection using xrandr
xrandrOutput=`xrandr --query`
leftGeometry=`echo "$xrandrOutput" | grep -P "\sconnected" \
                | grep -oP '\d+x\d+\+0\+\d+'`
rightGeometry=`echo "$xrandrOutput" | grep -P "\sconnected" \
                | grep -oP '\d+x\d+\+\d{2,}\+\d+'`
primaryGeomtry=`echo "$xrandrOutput" | grep -P "\sconnected primary" \
                | grep -oP '\d+x\d+\+\d+\+\d+'`
if [[ "$primaryGeomtry" == "$leftGeometry" ]] ; then
    mainMonitor=left
elif [[ "$primaryGeomtry" == "$rightGeometry" ]] ; then
    mainMonitor=right
else
    echo "Failed to detect main monitor"
fi
leftMonitor=${leftGeometry%%+*}
rightMonitor=${rightGeometry%%+*}
pipSize=340x340
pipOffset=+30+20
 
# Implicit variables
leftMonitorWidth=${leftMonitor%x*}
 
echo "leftMonitor:  " $leftMonitor
echo "rightMonitor: " $rightMonitor
echo "pipSize:      " $pipSize
echo "pipOffset:    " $pipOffset
echo "mainMonitor:  " $mainMonitor
 
########################
if  [[ ! "$mainMonitor" == "left" ]] && \
    [[ ! "$mainMonitor" == "right" ]] ; then
    echo "Bad mainMonitor variable"
    exit
fi
 
if [ ! $# -eq 2 ] ; then
    echo "Usage: ./recordscreen delay outputfolder"
    exit
fi
 
if [ ! -d $outputFolder ] ; then
    echo "Output folder does not exist"
    exit
fi
 
gsettings set org.gnome.desktop.sound event-sounds false
 
# $1:output
function captureScreenDump {
    xwd -root -out $1
}
 
# $1:input-file $2:output-file
function prepareThumbnail {
    if [[ "$mainMonitor" == "left" ]] ; then
        geometry=${rightMonitor}+${leftMonitorWidth}+0
    else
        geometry=${leftMonitor}+0+0
    fi
    convert $1 -crop $geometry -thumbnail $pipSize \
        -bordercolor "#881111aa" -border 2 \
        \( +clone -background black -shadow 100x3+2+2 \) \
        +swap -background none -layers merge +repage $2
}
 
# $1:captured-image  $2:pip-thumbnail  $3:output-frame
function prepareFinalFrame {
    geometry=
    if [[ "$mainMonitor" == "left" ]] ; then
        geometry=${leftMonitor}+0+0
    else
        geometry=${rightMonitor}+${leftMonitorWidth}+0
    fi
    convert -crop $geometry ${1} ${1}.tmp.xwd
    convert -composite ${1}.tmp.xwd $2 -gravity SouthEast \
        -geometry $pipOffset $3
    rm ${1}.tmp.xwd
}
 
# $1:counter  $2:folderOuput
function doFrameCapture {
    # Filenames for capture and right screen
    outfile=`printf '%.5d' $1`-`date +"%Y-%m-%d-%s"`
    capture=${2}/capture/${outfile}
    pip=${2}/pip/${outfile}
 
    # Capture both screens at the same time
    captureScreenDump $capture.xwd
    prepareThumbnail $capture.xwd $pip.png
 
    # Create final frame from left frame and thumbnail of right frame
    outfileFinal=${2}/frames/`printf '%.5d' $1`
    prepareFinalFrame ${capture}.xwd ${pip}.png ${outfileFinal}.png
 
    # Cleanup:
   rm -f $capture.xwd $pip.png
}
 
function finalize() {
    rm -f $outputFolder/{capture,pip}/*
    rmdir $outputFolder/{capture,pip}
    gsettings set org.gnome.desktop.sound event-sounds true
    exit
}
trap finalize SIGINT SIGTERM
 
###################  PROGRAM CODE STARTS  ###################
delay=$1
outputFolder=$2
curr=0
if [ -e $outputFolder/frames ] ; then
    lastFrame=`ls -1 $outputFolder/frames/ | tail -n 1`
    curr=`echo ${lastFrame%.*} | sed 's/^0*//'`
    while true; do
        read -p "Recording in progress (currently $curr frames)
Do you wish to continue at frame "$((curr+1))"? [y/n] " yn
        case $yn in
            [Yy]* ) break;;
            [Nn]* ) exit;;
            * ) echo "Please answer yes or no.";;
        esac
    done
fi
 
if [ ! -e $outputFolder/capture ] ; then mkdir $outputFolder/capture ; fi
if [ ! -e $outputFolder/pip ]     ; then mkdir $outputFolder/pip ;     fi
if [ ! -e $outputFolder/frames ]  ; then mkdir $outputFolder/frames fi
 
while sleep $delay;
do
    ((curr++))
    (doFrameCapture $curr $outputFolder &)
done
####################  -----------------  ####################
Gist on GitHub

2. Demo

If the above script for some reason skips a frame, this will cause problems when creating a video from them. A quick fix is to create a symlink to the previous file, for any missing frame.

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
lastFrame=`ls -1 frames/ | tail -n 1`
nframes=`echo ${lastFrame%.*} | sed 's/^0*//'`
 
for i in $(seq 1 1 $nframes)
do
    curr=`printf '%.5d.png' $i`
    prev=`printf '%.5d.png' $((i-1))`
    if [ ! -e frames/$curr ] ; then
       echo "$prev -> $curr"
       ln -s $prev frames/$curr
    fi
done

A 30 fps video, consuming 15 frames for each second in the output, can be made using ffmpeg with the following:

1
2
ffmpeg -r 15 -i frames/%05d.png -c:v \
   libx264 -r 30 -pix_fmt yuv420p timelapsevideo.mp4

While I wrote this post, I had the screen recording script running, and this is the produced video: