Skip to content

Instantly share code, notes, and snippets.

@LeaPhant
Last active December 18, 2025 17:52
Show Gist options
  • Select an option

  • Save LeaPhant/92c5ea0e4d3e4823d3ad931458c9449f to your computer and use it in GitHub Desktop.

Select an option

Save LeaPhant/92c5ea0e4d3e4823d3ad931458c9449f to your computer and use it in GitHub Desktop.

OBS Replay Scripts

Scripts to save a replay from OBS to preview it or export a clip. Ideal for assigning to a global hotkey.

Requires OBS WebSocket enabled without authentication to work without modifications.

Tools -> WebSocket Server Settings

  • [x] Enable WebSocket server
  • [ ] Enable Authentication

Scripts

#!/bin/sh
# open last replay in avidemux with output settings
# depends on obs-cmd and avidemux-qt
# arch linux: paru -S obs-cmd avidemux-qt
# output 9mb video (audio not accounted for)
OUTPUT_SIZE=9
# resize to 720p
RESOLUTION_WIDTH=1280
RESOLUTION_HEIGHT=720
OLD_REPLAY=$(obs-cmd replay last-replay)
REPLAY=$OLD_REPLAY
obs-cmd replay save
# wait for replay to finish writing
while [[ "$REPLAY" == "$OLD_REPLAY" ]]; do
REPLAY=$(obs-cmd replay last-replay)
sleep 0.1
done
REPLAY_PATH=$(obs-cmd replay last-replay | grep "path": | awk -F\:\ '{print $2}')
# write a temporary script to load the video and set options in avidemux
cat > /tmp/replay-script.py<< EOF
adm = Avidemux()
if not adm.loadVideo("$REPLAY_PATH"):
raise("Cannot load $REPLAY_PATH")
adm = Avidemux()
adm.videoCodec("x264", "useAdvancedConfiguration=False", "general.params=2PASS=$OUTPUT_SIZE", "general.threads=0", "general.preset=fast", "general.tuning=none", "general.profile=high", "general.fast_decode=False", "general.zero_latency=False"
, "general.fast_first_pass=True", "general.blueray_compatibility=False", "general.fake_interlaced=False", "level=-1", "vui.sar_height=1", "vui.sar_width=1", "vui.overscan=0", "vui.vidformat=5", "vui.fullrange=False"
, "vui.colorprim=2", "vui.transfer=2", "vui.colmatrix=2", "vui.chroma_loc=0", "MaxRefFrames=3", "MinIdr=25", "MaxIdr=250", "i_scenecut_threshold=40", "intra_refresh=False", "MaxBFrame=3", "i_bframe_adaptive=1"
, "i_bframe_bias=0", "i_bframe_pyramid=2", "b_deblocking_filter=True", "i_deblocking_filter_alphac0=0", "i_deblocking_filter_beta=0", "cabac=True", "interlaced=False", "constrained_intra=False", "tff=True"
, "fake_interlaced=False", "analyze.b_8x8=True", "analyze.b_i4x4=True", "analyze.b_i8x8=True", "analyze.b_p8x8=True", "analyze.b_p16x16=False", "analyze.b_b16x16=False", "analyze.weighted_pred=2", "analyze.weighted_bipred=True"
, "analyze.direct_mv_pred=1", "analyze.chroma_offset=0", "analyze.me_method=1", "analyze.me_range=16", "analyze.mv_range=-1", "analyze.mv_range_thread=-1", "analyze.subpel_refine=7", "analyze.chroma_me=True"
, "analyze.mixed_references=True", "analyze.trellis=1", "analyze.psy_rd=1.000000", "analyze.psy_trellis=0.000000", "analyze.fast_pskip=True", "analyze.dct_decimate=True", "analyze.noise_reduction=0", "analyze.psy=True"
, "analyze.intra_luma=11", "analyze.inter_luma=21", "ratecontrol.rc_method=0", "ratecontrol.qp_constant=0", "ratecontrol.qp_min=10", "ratecontrol.qp_max=51", "ratecontrol.qp_step=4", "ratecontrol.bitrate=0"
, "ratecontrol.rate_tolerance=1.000000", "ratecontrol.vbv_max_bitrate=0", "ratecontrol.vbv_buffer_size=0", "ratecontrol.vbv_buffer_init=1", "ratecontrol.ip_factor=1.400000", "ratecontrol.pb_factor=1.300000"
, "ratecontrol.aq_mode=1", "ratecontrol.aq_strength=1.000000", "ratecontrol.mb_tree=True", "ratecontrol.lookahead=40")
adm.addVideoFilter("swscale", "width=$RESOLUTION_WIDTH", "height=$RESOLUTION_HEIGHT", "algo=1", "sourceAR=0", "targetAR=0", "lockAR=True", "roundup=0")
adm.audioClearTracks()
adm.setSourceTrackLanguage(0,"und")
if adm.audioTotalTracksCount() <= 0:
raise("Cannot add audio track 0, total tracks: " + str(adm.audioTotalTracksCount()))
adm.audioAddTrack(0)
adm.audioCodec(0, "copy")
adm.audioSetDrc2(0, 0, 1, 0.001, 0.2, 1, 2, -12)
adm.audioSetEq(0, 0, 0, 0, 0, 880, 5000)
adm.audioSetChannelGains(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
adm.audioSetChannelDelays(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
adm.audioSetChannelRemap(0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8)
adm.audioSetShift(0, 0, 0)
adm.setContainer("MP4", "muxerType=0", "optimize=1", "forceAspectRatio=False", "aspectRatio=1", "displayWidth=$RESOLUTION_WIDTH", "rotation=0", "clockfreq=0")
EOF
QT_QPA_PLATFORM=xcb avidemux3_qt5 --run /tmp/replay-script.py
# clean up after closing avidemux
rm /tmp/replay-script.py
rm "$REPLAY_PATH"
#!/bin/sh
# open last replay in mpv
# to export a clip use https://github.com/ekisu/mpv-webm
# depends on obs-cmd and mpv
# arch linux: paru -S obs-cmd mpv
OLD_REPLAY=$(obs-cmd replay last-replay)
REPLAY=$OLD_REPLAY
obs-cmd replay save
# wait for replay to finish writing
while [[ "$REPLAY" == "$OLD_REPLAY" ]]; do
REPLAY=$(obs-cmd replay last-replay)
sleep 0.1
done
REPLAY_PATH=$(obs-cmd replay last-replay | grep "path": | awk -F\:\ '{print $2}')
mpv "$REPLAY_PATH"
# clean up after closing mpv
rm "$REPLAY_PATH"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment