Sellers28
Online2

Automatic GOTV demo recording for CS2 on Metamod:Source (Pisex framework).
Alternative to K4-GOTV-SwiftlyS2, but without SwiftlyS2 — a pure Metamod plugin.
split_per_round) — a separate file per round.{map} {date} {time} {datetime} {round} {players} {server}.min_duration seconds are deleted automatically.cleanup_hours — on map start and via timer.idle_stop_seconds..dem itself as an attachment,curl/rclone/gzip/…) — this handles!demo marks the current recording as neededonly_requested 1, everything else is deleted).cs2-utils (IUtilsApi) and cs2-players (IPlayersApi) — mandatorytv_enable 1addons/gotv_demos/gotv_demos.so
addons/metamod/gotv_demos.vdf
addons/configs/gotv_demos.ini
Everything else is in addons/configs/gotv_demos.ini, the file is thoroughly commented.
After the first start, check the server console:
[GOTV] demo directory: /home/cs2/game/csgo/demos/
[GOTV] startup: server="..." addr="..." tv_enable=1 webhook=set http=pending
If the path was detected incorrectly, specify the absolute path to csgo/ in game_path.
In the dedicated server console (pterodactyl, screen, stdin), only
real ConCommands with the mm_ prefix work:
| Command | Description |
|---|---|
mm_gotv_status | Full state: paths, cvars, whether recording is active |
mm_gotv_record | Start recording |
mm_gotv_stop | Stop and save |
mm_gotv_cleanup | Run cleanup of old files |
mm_gotv_dir | Show demo folder contents and waiting queue |
In chat and client console — via cs2-utils:
| Client Console | Chat | Description |
|---|---|---|
gotv_status | !gotvstatus | Same state, output to player console |
gotv_record | !gotvrecord | Start recording |
gotv_stop | !gotvstop | Stop and save |
gotv_demo | !demo, /demo | Player asks to save the current demo |
Permissions — gotv_demos/admin (configured in admin_permission); !demo is available to everyone.
Standard webhook accepts attachments up to 10 MB — hence max_upload_mb defaults to 9.
A file exceeding the limit is simply not attached, the metadata embed is still sent.
A full match in a single file almost always exceeds 10 MB, so for attachments, it makes sense
to use either split_per_round 1 or the combination of upload_command + upload_url_format.
.demtv_record demos/foo creates a file demos/foo — without an extension, and a player cannot open such a demo. The plugin looks for both name forms, and upon finishing recording, appends .dem itself — before uploading, so the correct name goes to FTP and Discord. Can be disabled via force_dem_extension 0.
While the file is still without an extension, cleanup identifies the demo by the absence of a dot in the name — therefore demo_directory must be a separate folder, and not csgo/ itself (if you specify csgo/, cleanup will refuse to work instead of deleting excess files).
Everything except A-Za-z0-9._- is stripped from the name; spaces become _, consecutive _ are collapsed, and edges are trimmed. Therefore, decorations in the hostname (▌ CS2.RU▌ PUBLIC #1) result in CS2.RU_PUBLIC_1, not <u>CS2.RU</u>PUBLIC_1__. If the hostname is entirely non-Latin, {server} will yield nothing — set a Latin name in server_name.
The pending demo queue lives in memory, so kill -9 loses it. To prevent demos from lingering on the disk forever, upon startup and on every map change, the plugin scans the folder: anything it does not track itself and which hasn't changed for the last minute is considered a ready demo — uploaded and sent to Discord. Can be disabled via orphan_scan 0.
For such "recovered" demos, duration, map, and rounds are unknown, so min_duration and only_requested do not apply to them, and only the name, size, and time remain in the embed.
If GOTV on the server failed to start, tv_record still creates a file: the engine writes the header, string tables, and Stop, but not a single frame of gameplay. Such a file weighs about 120 KB regardless of recording duration and contains exactly 18 packets, of which Packet is zero.
An indicator that this is the exact issue: the file is closed normally (there is Stop and FileInfo), live players are visible in userinfo, and playback_time is zero. The same recording duration on another server yields megabytes.
The first thing to check is GSLT (Steam Game Server Login Token). If the token is not specified (in pterodactyl this is the STEAM_ACC variable) or has expired, the server does not log into Steam, the GOTV instance is not created, and tv_record writes exactly such an empty file. At the same time, tv_enable shows 1, there might be no errors in the console, and the server plays as usual — which is why the cause takes a long time to find. This is precisely what caused empty demos on 4 out of 10 servers; after adding the token, everything started working immediately.
Cause #2 — bot_quota_mode fill kicks the GOTV bot. At the end of warmup, the game adjusts the number of clients to bot_quota, and SourceTV is just another BOT to the engine.
In the server console:
Warmup period ended
SourceTV kicked by Console (NETWORK_DISCONNECT_KICKED)
Syfers kicked by Console (NETWORK_DISCONNECT_KICKED)
The GOTV server itself does not turn off — the port is listened to, status prints sourcetv[0], tv_enable is 1. Only the client is dead. Checked via tv_status: frames N / ticks N stops growing and freezes at the moment of the kick, although Online continues. After this, tv_record silently does nothing, and GOTV[0] not active. falls into the server log — the command prints neither error nor confirmation to the console. Fixed by configuring bots on the server, not the plugin.
Further down the list — tv_enable, tv_maxclients, whether the slot for the GOTV bot is free, whether tv_enable was set after map load. The plugin prints these variables to the log at each recording start and via the mm_gotv_find command — the logs of a working and non-working server can be compared directly.
Manual check on a problematic server: tv_record test, wait half a minute, tv_stoprecord. If the file is still ~120 KB, the plugin is not at fault.
min_size_kb 200 does not cure the cause, but prevents empty files from going to Discord and storage.
A demo is closed by any of the following:
split_per_round 1;max_duration seconds from the start of recording (0 = disabled);cs_win_panel_match);idle_stop_seconds.Worth mentioning separately is the case of a single-map server that never changes. If, on top of that, the match never ends (infinite mode), then with split_per_round 0, the demo will only be closed when the server becomes empty — the file will grow for hours and almost certainly stop fitting into the Discord attachment limit. For such a server, set max_duration 1800 (or 3600), or leave split_per_round 1.
You cannot split by size: while recording is ongoing, the file does not yet exist on disk — CS2 drops it only at the end of recording, so there is nothing to measure the size from. The restriction works by time.
The engine closes .dem asynchronously — sometimes seconds after tv_stoprecord, sometimes only upon map change. Therefore, the plugin does not guess with a delay: once a second it checks the path and considers the demo ready when the file has appeared and its size has not changed for finalize_stable seconds. Waiting recordings survive map changes.
In addition, after each stop, a silence period of finalize_grace seconds is active, during which a new recording does not start: if tv_record is given immediately, the engine does not have time to close the previous file and it is lost completely.
If the file still hasn't appeared within finalize_timeout, a listing of the demos folder is dumped to the log — making it immediately visible if the engine named the file differently or if it truly isn't there. The same can be done via the mm_gotv_dir command.
debug 1 in the config → addons/logs/gotv_demos.txt: recording start/stop, file drop reasons, Discord response codes, PID, and upload command result.