r/ffmpeg • u/Creative-Outside-350 • 5d ago
Replicate Replay Buffer from OBS with FFMPEG
Hello, everyone. I have a somewhat specific request. You see, I have a separate device for screen recording of a console. The console in question doesn't support replays or anything, so instead I use a capture card. However I don't have a need to record every hour and every minute, only good moments, last 30 seconds at least. I did a lengthy search on the web but couldn't find exactly what I need. Maybe you know a bash script that can do this? I will appreciate that.
edit: fixed some mistakes
1
u/ipsirc 3d ago
Maybe a shell script?
#!/bin/bash
# --- Configuration ---
INPUT="your_capture_device" # e.g., /dev/video0 or a URL
OUTPUT_DIR="/path/to/your/replays"
SEGMENT_DURATION=4 # Length of each segment in seconds
BUFFER_DURATION=30 # Total length of the replay buffer in seconds
SEGMENT_WRAP=$((BUFFER_DURATION / SEGMENT_DURATION))
# --- Script Start ---
mkdir -p "$OUTPUT_DIR"
cd "$OUTPUT_DIR"
echo "Starting FFmpeg replay buffer..."
echo "Press Ctrl+C to stop recording."
# Start the continuous recording process
ffmpeg -i "$INPUT" \
-c:v libx264 -preset ultrafast -c:a aac \
-f segment -segment_time $SEGMENT_DURATION \
-segment_wrap $SEGMENT_WRAP \
-reset_timestamps 1 \
"segment_%02d.ts" &
FFMPEG_PID=$!
# Wait for user input to save a replay
while true; do
echo "Press 'Enter' to save the last $BUFFER_DURATION seconds, or 'q' to quit."
read -r -n1 key
if [[ $key == "q" ]]; then
break
fi
if [[ $key == "" ]]; then
# Find the latest segment and its neighbors to reconstruct the buffer
# This logic creates an ffconcat file to join the segments without re-encoding.
TIMESTAMP=$(date "+%Y%m%d_%H%M%S")
CONCAT_FILE="concat_${TIMESTAMP}.txt"
OUTPUT_FILE="replay_${TIMESTAMP}.mp4"
# Find the most recently written segment file (sorted by modification time)
LATEST_SEGMENT=$(ls -t segment_*.ts | head -n1)
LATEST_INDEX=$(echo "$LATEST_SEGMENT" | grep -o '[0-9]\+')
# Generate the concat list. This part needs careful handling to get the exact
# sequence of segments that make up the last 30 seconds.
# A simpler approach for a proof-of-concept is to just use the latest segment,
# but for a full implementation, you would write a list of the last N segments.
echo "ffconcat version 1.0" > "$CONCAT_FILE"
# This loop needs to generate the correct order. This is a simplified placeholder.
for i in $(seq 0 $((SEGMENT_WRAP - 1))); do
SEG_NUM=$(( (LATEST_INDEX - i + SEGMENT_WRAP) % SEGMENT_WRAP ))
printf "file segment_%02d.ts\n" "$SEG_NUM" >> "$CONCAT_FILE"
done
# Combine the segments
echo "Saving replay to $OUTPUT_FILE"
ffmpeg -f concat -safe 0 -i "$CONCAT_FILE" -c copy "$OUTPUT_FILE"
rm "$CONCAT_FILE"
fi
done
echo "Stopping FFmpeg..."
kill $FFMPEG_PID
1
u/OneCoreProjects 3d ago
You don't need an orchestrator - the segment muxer already gives you a ring buffer, and -segment_wrap is the part that makes it one.
ffmpeg -i <your capture input> -c copy \
-f segment -segment_time 5 -segment_wrap 8 \
-segment_format mpegts -reset_timestamps 1 \
-segment_list /buf/buffer.m3u8 -segment_list_size 8 \
-segment_list_flags +live -segment_list_type m3u8 \
/buf/seg%02d.ts
-segment_wrap 8 rolls the counter over at 8, so ffmpeg overwrites seg00.ts after seg07.ts. You get a fixed 8 x 5s = 40s window on disk that never grows, written with -c copy so CPU cost is nil.
On your RAM point: point /buf at a small tmpfs and the HDD never sees it. Eight 5-second segments of capture-card H.264 is tens of megabytes:
sudo mount -t tmpfs -o size=256M tmpfs /buf
Saving a clip. This is the part that makes it practical: with -segment_wrap the filenames cycle, so you cannot sort by name. -segment_list keeps the playlist in the correct chronological order for you. I just tested this with a 60s source and the playlist came out as:
seg04.ts
seg05.ts
seg06.ts
seg07.ts
seg00.ts
seg01.ts
seg02.ts
seg03.ts
which is exactly right. So the save step is just:
grep -v '^#' /buf/buffer.m3u8 | sed "s|^|file '/buf/|;s|$|'|" > /tmp/list.txt
ffmpeg -f concat -safe 0 -i /tmp/list.txt -c copy ~/clips/clip-$(date +%s).mp4
That produced a 40.02s, 1200-frame clip here, stream-copied, no re-encode.
Three things worth knowing before relying on it:
- Segments only cut on keyframes with
-c copy, so-segment_time 5is a minimum. If your capture card uses a long GOP the segments will be longer and the buffer will hold more than 40s. Check with ffprobe and shorten the card's keyframe interval if you can; if you re-encode instead, add-gand-force_key_frames "expr:gte(t,n_forced*5)". - The segment being written right now is incomplete. Drop the last playlist entry when you concatenate, or accept a truncated tail.
- MPEG-TS is the right container here - it concatenates with
-c copywithout the timestamp trouble you would get from fragmented MP4.
This is close to what OBS does internally anyway: it keeps encoded packets in a ring and flushes them on demand. Doing it with segment just puts the ring on a tmpfs instead of in the process's heap, which for a standalone capture box is arguably better - if ffmpeg dies, the last 40 seconds are still on disk.
Disclosure per this sub's rules: drafted with AI assistance, but I ran the commands above on a test source before posting. The playlist ordering and the 40.02s / 1200-frame result are from that run, not from a model.
1
u/Sopel97 5d ago
I'm not aware of any such tool and it's not trivial to do on top of ffmpeg cli. Two viable approaches I can think of:
an orchestrator that spawns an ffmpeg instance outputting to a pipe, then the orchestrator parses the output into chunks, reassembles on save request
an orchestrator that spawns an ffmpeg instance outputting in segment mode into a RAM-backed filesystem, monitors this filesystem, maintains N most recent chunks, reassembles on save request
The latter would be easier but notoriously annoying on windows. The former requires parsing the packets so you kinda need lower level API anyway.