record-lecture.sh: How I Recorded My Covid Lectures on a Single Monitor
Script: GitLab: Fabian Untermoser / dot-files - i3/.local/bin/record-lecture.sh
Will most likely only work on Linux using
i3on X11.
During the pandemic in 2022, my university moved to fully remote teaching. Almost everything happened on Microsoft Teams.
At the same time, I was working part-time as a software engineer from the same machine: a 16” laptop with one physical monitor. I wanted to record lectures so I could review them later, but I did not want my recordings polluted by random window changes, notifications, or whatever I was doing on my desktop.
So instead of paying better attention in class, I engineered my way out of the problem.
The result was a setup that let me:
- Record clean lecture videos
- Keep using my desktop while recording
- Isolate lecture audio from everything else
- Automatically compress and archive the recordings
The Setup at a Glance
My workflow consisted of:
- A virtual second monitor that held the lecture fullscreen (
xrandr) - Virtual audio devices that captured only Teams + microphone (
pactl,pavucontrol) - OBS Studio for recording, plus Window Projector for a small preview (
obs,obs-cli) - Two MS Teams instances: one for recording, one for interaction (native Teams +
nativefier-packaged Teams app) - A
record-lecture.shscript that automated the whole setup (Bash +i3-msg+dunstctl) - A small Debian VM called
compressorthat batch-compressed recordings (syncthing+cron+compress.sh)
i3-virtualscreen.sh Creating a Virtual Monitor
The key idea was simple: the lecture should live on a display I do not actively use.
I created a virtual monitor and moved the “recorded” Teams window there. OBS then captured that virtual display, not my real desktop. This gave me a stable recording surface while keeping my actual monitor free for normal work.
In practice, that step was just one command in my script:
i3-virtualscreen.sh --on down 1000Small note on how this works internally:
- It takes the currently active mode from
xrandr --current(resolution + refresh rate). - It creates a matching modeline via
gtfand registers it on the first disconnected output (xrandr --newmode+xrandr --addmode). - It enables that output with a position argument (
--right-of,--left-of, or padded--posfordown/right). --offdisables the output again and removes the mode from that connector.
i3-virtualscreen.sh script: GitLab: i3/.local/bin/i3-virtualscreen.sh · cc8bb5539c1a0c294d1e8a6a8f8fc5ee0082e3e8 · Fabian Untermoser / dot-files
Because I still wanted to occasionally check what was being recorded, I used OBS Window Projector to open a small draggable preview window. It worked like a mini external monitor, but without needing extra hardware.
Creating a Virtual Audio Device
Video was only half the battle. Audio quality mattered just as much.
I used virtual audio devices so the recording captured:
- Teams call audio
- My microphone
And ignored:
- Music in the background
- Notification sounds
- Other app audio
This separation prevented the classic “great video, unusable audio” failure mode.
The core piece was creating a combined sink with PulseAudio:
pactl load-module module-combine-sink sink_name=combined slaves="$active_sink"Then using pavucontrol I could assign Teams and my mic to this virtual sink.
OBS Recording Setup & Window Projector
OBS was configured to record:
- The virtual monitor as display input
- The virtual lecture/mic audio devices as audio sources
This gave me consistent output no matter what happened on my real desktop. I could switch workspaces, open code editors, or handle unrelated tasks without polluting the lecture recording.
To actually see the lecture/recording view myself, I used the Window Projector preview. This was also useful as a quick sanity check before and during class. I mapped a key in i3 to toggle this window.
bindsym $mod+$alt+f $exe $msg '[app_id="com.obsproject.Studio" title="Windowed Projector*"] scratchpad show'Running Two Teams Instances
I needed two Teams sessions at once:
- Teams instance 1 joined the lecture and was moved to the virtual monitor
- Teams instance 2 stayed on my desktop for chat, assignments, or interaction
The first was native Teams. The second was a repackaged Teams app built with Nativefier. I used --internal-urls '.*' so internal Teams navigation stayed inside the packaged app.
Example Nativefier command:
nativefier --tray --name "teams-secondary" "https://teams.microsoft.com/" --internal-urls '.*'Without this split, I had to choose between “good recording” and “usable working session.” With this split, I got both.
GitHub: nativefier/nativefier: Make any web page a desktop application
Automatic Compression with a Debian VM
Raw lecture recordings were large, so I offloaded compression to a small Debian VM in my homelab (compressor).
I used Syncthing to automatically move new recordings from my laptop to that VM.
Workflow:
- Recordings synced from my laptop to the VM via Syncthing
- A cronjob on the VM ran a compression script
- Compressed files were pushed to my media archive on Nextcloud
Core compression command:
ffmpeg -y -i "$f" -vcodec libx264 -crf 20 "$compressedFile"My script watched a videos-in/ folder, wrote compressed files to videos-out/, and removed the original input once compression succeeded.
That gave me smaller files, easier sharing, and a clean archive for later review.
The record-lecture.sh Script
I wrapped the entire flow in one script so starting class became a single action.
What the script handled:
- Create virtual audio devices
- Create virtual monitor
- Launch OBS and its Window Projector
- Run my Wacom setup script (if the tablet was connected)
- Move the recording Teams instance to the virtual monitor
- Pause desktop notifications
Two representative commands from the automation were:
i3-msg -q '[class="Microsoft Teams*"] move container to workspace TEAMS, floating disable'
obs-cli OpenProjectorShutdown path:
record-lecture.sh --offremoved virtual devices/monitor and re-enabled notifications
Hotkeys:
F4to start the setupShift+F4to tear it down
Quirks
One weird issue remained: if I joined the lecture call before launching the script, my PC sometimes froze for a short time. I never fully diagnosed the root cause, so my workaround was procedural: always run the script first, then join.
What I Learned
- Linux lets you build surprisingly custom workflows from small tools.
- Good recording quality is mostly about routing and isolation, not just resolution.
- In OBS, always verify audio devices before you hit record.
- Accidentally closing the Window Projector is annoying; reopening it manually is possible but slow. I wanted to automate it through
obs-websocket, but it was broken on Arch at that time.
Closing Thoughts
This setup started as a “single-monitor pain” hack and ended up becoming one of my favorite personal automations. It gave me clean lecture recordings, reduced context-switch stress, and taught me a lot about display/audio plumbing on Linux.
If you’re in a similar situation, the main principle is simple: isolate your recording pipeline from your working environment, then automate setup and teardown so you actually use it.