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 i3 on 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.sh script that automated the whole setup (Bash + i3-msg + dunstctl)
  • A small Debian VM called compressor that 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 1000

Small note on how this works internally:

  • It takes the currently active mode from xrandr --current (resolution + refresh rate).
  • It creates a matching modeline via gtf and 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 --pos for down/right).
  • --off disables 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 OpenProjector

Shutdown path:

  • record-lecture.sh --off removed virtual devices/monitor and re-enabled notifications

Hotkeys:

  • F4 to start the setup
  • Shift+F4 to 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.