Perfect obs studio setting for live streaming
best OBS Studio settings for live streaming 2026 recommended
| Your GPU | Encoder to Use |
|---|---|
| NVIDIA | NVENC H.264 (hardware - lowest CPU load) |
| AMD | AMF H.264 |
| Intel | QuickSync (QSV) H.264 |
| No GPU / weak GPU | x264 (CPU software encoder) |
| Platform | Max Video Bitrate |
|---|---|
| Twitch (non-partner) | 6,000 Kbps |
| Twitch (partner) | up to 8,000 Kbps |
| YouTube Live | 8,000 - 9,000 Kbps |
| Facebook Live | 4,000 Kbps |
| Kick | 8,000 Kbps |
| Setting | Recommended Value | Why |
|---|---|---|
| Rate Control | CBR (Constant Bitrate) | Required by all major platforms for stable ingest |
| Keyframe Interval | 2 seconds | Required by Twitch/YouTube; ensures proper stream segments |
| H.264 Profile | High | Best quality at same bitrate |
| Encoder Preset | Quality (NVENC) / medium (x264) | Balance of quality vs encode time |
| Look-Ahead (NVENC) | Enable for FPS shooters | Allocates bitrate during fast motion |
| Downscale Filter | Lanczos (36 samples) | Best quality when scaling resolution down |
Create a quick reference PDF with these OBS settings for your desktop
mkdir -p /tmp/workspace/obs-settings && ls /tmp/workspace/obs-settings/
~/obs-settings/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import KeepTogether
OUTPUT = "/tmp/workspace/obs-settings/OBS_Settings_Quick_Reference.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=15*mm,
leftMargin=15*mm,
topMargin=14*mm,
bottomMargin=14*mm,
title="OBS Studio Quick Reference - Live Streaming Settings",
author="Orris AI"
)
# ── Colours ──────────────────────────────────────────────────────────────────
OBS_DARK = colors.HexColor("#1a1a2e") # deep navy (header bg)
OBS_BLUE = colors.HexColor("#0f3460") # mid blue
OBS_ACCENT = colors.HexColor("#e94560") # OBS red-ish accent
OBS_LIGHT = colors.HexColor("#eaf0fb") # very light blue tint (table rows)
WHITE = colors.white
DARK_TEXT = colors.HexColor("#1c1c2e")
MID_GREY = colors.HexColor("#5a6070")
ROW_ALT = colors.HexColor("#f4f6ff")
# ── Styles ────────────────────────────────────────────────────────────────────
base = getSampleStyleSheet()
title_style = ParagraphStyle(
"TitleStyle",
parent=base["Title"],
fontSize=24,
leading=30,
textColor=WHITE,
alignment=TA_CENTER,
fontName="Helvetica-Bold",
)
subtitle_style = ParagraphStyle(
"SubTitle",
parent=base["Normal"],
fontSize=11,
leading=14,
textColor=colors.HexColor("#c8d0e8"),
alignment=TA_CENTER,
fontName="Helvetica",
)
section_style = ParagraphStyle(
"SectionHeader",
parent=base["Normal"],
fontSize=12,
leading=15,
textColor=WHITE,
fontName="Helvetica-Bold",
leftIndent=4,
)
body_style = ParagraphStyle(
"Body",
parent=base["Normal"],
fontSize=9,
leading=13,
textColor=DARK_TEXT,
fontName="Helvetica",
)
small_style = ParagraphStyle(
"Small",
parent=base["Normal"],
fontSize=8,
leading=11,
textColor=MID_GREY,
fontName="Helvetica",
alignment=TA_CENTER,
)
tip_style = ParagraphStyle(
"Tip",
parent=base["Normal"],
fontSize=8.5,
leading=12,
textColor=DARK_TEXT,
fontName="Helvetica",
leftIndent=6,
)
# ── Helper: section header ────────────────────────────────────────────────────
def section_header(text, color=OBS_BLUE):
return Table(
[[Paragraph(text, section_style)]],
colWidths=[180*mm],
style=TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("ROUNDEDCORNERS", [4]),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
])
)
# ── Helper: data table ────────────────────────────────────────────────────────
def make_table(headers, rows, col_widths):
header_row = [Paragraph(f"<b>{h}</b>", ParagraphStyle(
"TH", parent=base["Normal"], fontSize=9, textColor=WHITE,
fontName="Helvetica-Bold", leading=12)) for h in headers]
data = [header_row]
for i, row in enumerate(rows):
styled = []
for cell in row:
styled.append(Paragraph(cell, ParagraphStyle(
"TD", parent=base["Normal"], fontSize=8.5, textColor=DARK_TEXT,
fontName="Helvetica", leading=12)))
data.append(styled)
style = TableStyle([
("BACKGROUND", (0, 0), (-1, 0), OBS_BLUE),
("ROWBACKGROUNDS",(0, 1), (-1, -1), [WHITE, ROW_ALT]),
("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#d0d8ee")),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 7),
("RIGHTPADDING", (0, 0), (-1, -1), 7),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("ROUNDEDCORNERS", [3]),
])
return Table(data, colWidths=col_widths, style=style, repeatRows=1)
# ── Build content ─────────────────────────────────────────────────────────────
story = []
W = 180*mm
# ── HEADER BANNER ─────────────────────────────────────────────────────────────
banner = Table(
[[Paragraph("🎙 OBS Studio", title_style)],
[Paragraph("Live Streaming Quick Reference • 2026 Edition", subtitle_style)]],
colWidths=[W],
style=TableStyle([
("BACKGROUND", (0,0), (-1,-1), OBS_DARK),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 10),
("ALIGN", (0,0), (-1,-1), "CENTER"),
])
)
story.append(banner)
story.append(Spacer(1, 5*mm))
# ── 1. ENCODER ────────────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("🖥 Encoder Selection"),
Spacer(1, 2*mm),
make_table(
["Your GPU / CPU", "Encoder to Use", "Notes"],
[
["NVIDIA GPU", "NVENC H.264", "Best choice - lowest CPU load"],
["AMD GPU", "AMF H.264", "Hardware encoding via AMD VCE"],
["Intel GPU / iGPU", "QuickSync (QSV)", "Good on Intel 6th gen+"],
["No/Weak GPU", "x264 (CPU)", "Use 'medium' or 'fast' preset"],
],
[58*mm, 52*mm, 70*mm]
),
Spacer(1, 4*mm),
]))
# ── 2. PRESETS ────────────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("📺 Resolution & Bitrate Presets"),
Spacer(1, 2*mm),
make_table(
["Preset", "Resolution", "FPS", "Video Bitrate", "Best For"],
[
["A - Standard", "1920×1080", "30", "4,500 – 6,000 Kbps", "Talk / IRL / Professional"],
["B - Gaming", "1920×1080", "60", "6,000 – 9,000 Kbps", "FPS / Fast-motion Gaming"],
["C - Low Upload", "1280×720", "30/60","2,500 – 4,500 Kbps", "Limited upload speed"],
],
[32*mm, 34*mm, 18*mm, 46*mm, 50*mm]
),
Spacer(1, 4*mm),
]))
# ── 3. PLATFORM CAPS ─────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("📡 Platform Bitrate Caps"),
Spacer(1, 2*mm),
make_table(
["Platform", "Max Video Bitrate", "Notes"],
[
["Twitch (non-partner)", "6,000 Kbps", "Exceeding causes stream drop"],
["Twitch (partner)", "up to 8,000 Kbps", "Check your partner agreement"],
["YouTube Live", "8,000 – 9,000 Kbps","More tolerant of higher bitrates"],
["Facebook Live", "4,000 Kbps", "Keep audio + video under cap"],
["Kick", "8,000 Kbps", "Similar to YouTube"],
],
[52*mm, 48*mm, 80*mm]
),
Spacer(1, 4*mm),
]))
# ── 4. CORE SETTINGS ─────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("⚙️ Core Output Settings (Settings › Output › Advanced)"),
Spacer(1, 2*mm),
make_table(
["Setting", "Recommended Value", "Why"],
[
["Output Mode", "Advanced", "Unlocks all tuning options"],
["Rate Control", "CBR", "Required by all major platforms"],
["Keyframe Interval", "2 seconds", "Required by Twitch & YouTube"],
["H.264 Profile", "High", "Best quality at same bitrate"],
["Encoder Preset", "Quality (NVENC) / medium (x264)", "Balance quality vs CPU"],
["Look-Ahead (NVENC)", "ON for FPS shooters","Allocates bitrate during motion"],
["Downscale Filter", "Lanczos (36 samples)","Best quality when scaling down"],
],
[50*mm, 68*mm, 62*mm]
),
Spacer(1, 4*mm),
]))
# ── 5. VIDEO SETTINGS ────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("🎬 Video Settings (Settings › Video)"),
Spacer(1, 2*mm),
make_table(
["Setting", "Value"],
[
["Base (Canvas) Resolution", "Match your monitor e.g. 1920×1080"],
["Output (Scaled) Resolution","1920×1080 or 1280×720 per preset"],
["Frame Rate", "60 FPS (gaming) / 30 FPS (talk/IRL)"],
["Downscale Filter", "Lanczos"],
],
[70*mm, 110*mm]
),
Spacer(1, 4*mm),
]))
# ── 6. AUDIO SETTINGS ────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("🔊 Audio Settings (Settings › Audio)"),
Spacer(1, 2*mm),
make_table(
["Setting", "Value"],
[
["Sample Rate", "48 kHz"],
["Channels", "Stereo"],
["Desktop Audio Bitrate", "160 Kbps AAC"],
["Mic / Aux Bitrate", "160 Kbps AAC"],
["Mic Filters (must-have)","Noise Suppression • Noise Gate • Compressor"],
],
[70*mm, 110*mm]
),
Spacer(1, 4*mm),
]))
# ── 7. UPLOAD SPEED ───────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("🌐 Upload Speed Requirements"),
Spacer(1, 2*mm),
make_table(
["Stream Bitrate", "Minimum Upload Speed Needed"],
[
["4,500 Kbps", "≥ 7 Mbps upload"],
["6,000 Kbps", "≥ 9 Mbps upload"],
["8,000 Kbps", "≥ 12 Mbps upload"],
],
[90*mm, 90*mm]
),
Spacer(1, 2*mm),
Paragraph(
"Rule of thumb: your upload speed must be at least 1.5× your total stream bitrate to have headroom.",
tip_style
),
Spacer(1, 4*mm),
]))
# ── 8. QUICK TIPS ─────────────────────────────────────────────────────────────
tips = [
"Use the OBS <b>Auto-Configuration Wizard</b> (Tools menu) as a starting point.",
"Enable <b>Dynamic Bitrate</b> (Settings › Advanced) to auto-reduce bitrate during network spikes.",
"Always stream to the <b>ingest server closest to your location</b> for lowest latency.",
"Test with a <b>private/unlisted stream</b> first before going live.",
"A <b>lower stable bitrate</b> always beats a high choppy one - viewers will drop if they buffer.",
"Keep mic audio <b>dominant</b> in your audio mix so viewers can always hear you clearly.",
]
tips_rows = [[f"{'●'} {t}"] for t in tips]
tips_data = [[Paragraph(f"● {t}", tip_style)] for t in tips]
tips_table = Table(
tips_data,
colWidths=[W],
style=TableStyle([
("BACKGROUND", (0,0), (-1,-1), OBS_LIGHT),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#c8d4f0")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
])
)
story.append(KeepTogether([
section_header("💡 Quick Tips", color=colors.HexColor("#1a5276")),
Spacer(1, 2*mm),
tips_table,
Spacer(1, 4*mm),
]))
# ── FOOTER ────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width=W, thickness=0.5, color=colors.HexColor("#c0c8e0")))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"Generated by Orris AI • OBS Studio 2026 • obsproject.com",
small_style
))
# ── Build ──────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")
python /tmp/workspace/obs-settings/generate_pdf.py
OBS Settings Quick Reference
PDF Document · PDF
It is not showing 160 kbps setting
"160" in ~/obs-settings
~/obs-settings/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/obs-settings/generate_pdf.py'
~/obs-settings/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/obs-settings/generate_pdf.py'
~/obs-settings/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/obs-settings/generate_pdf.py'
~/obs-settings/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/obs-settings/generate_pdf.py'
python /tmp/workspace/obs-settings/generate_pdf.py
OBS Settings Quick Reference
PDF Document · PDF
×, –) that can sometimes cause ReportLab to silently drop text in certain environments.I am finding it difficult to navigate this settings
~/obs-settings/generate_nav_guide.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Polygon, Circle
from reportlab.graphics import renderPDF
from reportlab.platypus import Flowable
OUTPUT = "/tmp/workspace/obs-settings/OBS_Navigation_Guide.pdf"
W_PAGE = A4[0]
H_PAGE = A4[1]
MARGIN = 15 * mm
W = W_PAGE - 2 * MARGIN # usable width ~180mm
# ── Colours ───────────────────────────────────────────────────────────────────
C_DARK = colors.HexColor("#1a1a2e")
C_BLUE = colors.HexColor("#0f3460")
C_ACCENT = colors.HexColor("#e94560")
C_ORANGE = colors.HexColor("#f5a623")
C_GREEN = colors.HexColor("#27ae60")
C_LIGHT = colors.HexColor("#eaf0fb")
C_ROW_ALT = colors.HexColor("#f4f6ff")
C_GREY = colors.HexColor("#5a6070")
C_WHITE = colors.white
C_BORDER = colors.HexColor("#c0c8e0")
C_STEP_BG = colors.HexColor("#fff8e1")
C_STEP_BD = colors.HexColor("#f5a623")
C_NOTE_BG = colors.HexColor("#e8f5e9")
C_NOTE_BD = colors.HexColor("#27ae60")
C_WARN_BG = colors.HexColor("#fff3e0")
C_WARN_BD = colors.HexColor("#e67e22")
C_PATH_BG = colors.HexColor("#e8eaf6")
C_PATH_BD = colors.HexColor("#3949ab")
# ── Styles ────────────────────────────────────────────────────────────────────
base = getSampleStyleSheet()
def ps(name, parent="Normal", **kw):
return ParagraphStyle(name, parent=base[parent], **kw)
title_s = ps("Title2", fontSize=22, leading=28, textColor=C_WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER)
sub_s = ps("Sub2", fontSize=10, leading=14, textColor=colors.HexColor("#c8d0e8"),
fontName="Helvetica", alignment=TA_CENTER)
sec_s = ps("Sec2", fontSize=11, leading=14, textColor=C_WHITE,
fontName="Helvetica-Bold", leftIndent=4)
body_s = ps("Body2", fontSize=9, leading=13, textColor=colors.HexColor("#1c1c2e"),
fontName="Helvetica")
bold_s = ps("Bold2", fontSize=9, leading=13, textColor=colors.HexColor("#1c1c2e"),
fontName="Helvetica-Bold")
small_s = ps("Small2", fontSize=8, leading=11, textColor=C_GREY,
fontName="Helvetica", alignment=TA_CENTER)
path_s = ps("Path2", fontSize=9, leading=12, textColor=colors.HexColor("#1a237e"),
fontName="Helvetica-Bold", alignment=TA_CENTER)
step_num_s= ps("StepNum",fontSize=13, leading=16, textColor=C_ACCENT,
fontName="Helvetica-Bold", alignment=TA_CENTER)
step_s = ps("Step2", fontSize=9.5,leading=14, textColor=colors.HexColor("#1c1c2e"),
fontName="Helvetica")
note_s = ps("Note2", fontSize=8.5,leading=12, textColor=colors.HexColor("#1b5e20"),
fontName="Helvetica")
warn_s = ps("Warn2", fontSize=8.5,leading=12, textColor=colors.HexColor("#7f3b00"),
fontName="Helvetica")
code_s = ps("Code2", fontSize=9, leading=13, textColor=colors.HexColor("#0d0d0d"),
fontName="Courier")
th_s = ps("TH2", fontSize=9, leading=12, textColor=C_WHITE,
fontName="Helvetica-Bold")
td_s = ps("TD2", fontSize=8.5,leading=12, textColor=colors.HexColor("#1c1c2e"),
fontName="Helvetica")
# ── Helpers ───────────────────────────────────────────────────────────────────
def sec_hdr(text, color=C_BLUE):
return Table([[Paragraph(text, sec_s)]], colWidths=[W],
style=TableStyle([
("BACKGROUND", (0,0),(-1,-1), color),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("LEFTPADDING", (0,0),(-1,-1), 8),
]))
def make_table(headers, rows, col_widths):
data = [[Paragraph(f"<b>{h}</b>", th_s) for h in headers]]
for row in rows:
data.append([Paragraph(c, td_s) for c in row])
return Table(data, colWidths=col_widths, repeatRows=1,
style=TableStyle([
("BACKGROUND", (0,0),(-1,0), C_BLUE),
("ROWBACKGROUNDS",(0,1),(-1,-1), [C_WHITE, C_ROW_ALT]),
("GRID", (0,0),(-1,-1), 0.4, C_BORDER),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING", (0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 7),
("RIGHTPADDING", (0,0),(-1,-1), 7),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
def callout(text, style, bg, bd):
return Table([[Paragraph(text, style)]], colWidths=[W],
style=TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("LINEBELOW", (0,0),(-1,-1), 1.5, bd),
("LINEBEFORE", (0,0),(-1,-1), 3, bd),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 8),
]))
def path_box(path_text):
"""Renders a clickpath like Settings > Output > Encoding in a blue pill box."""
return Table([[Paragraph(path_text, path_s)]], colWidths=[W],
style=TableStyle([
("BACKGROUND", (0,0),(-1,-1), C_PATH_BG),
("LINEABOVE", (0,0),(-1,-1), 1, C_PATH_BD),
("LINEBELOW", (0,0),(-1,-1), 1, C_PATH_BD),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING", (0,0),(-1,-1), 5),
]))
def step_row(num, title, desc, value=None):
"""Single numbered step block."""
num_cell = Paragraph(str(num), step_num_s)
title_p = Paragraph(f"<b>{title}</b>", step_s)
desc_p = Paragraph(desc, step_s)
val_p = Paragraph(f'<font color="#0f3460"><b>Set to: {value}</b></font>', step_s) if value else None
inner = [title_p, desc_p]
if val_p:
inner.append(val_p)
content_cell = [i for i in inner]
# build as mini-table col
inner_tbl = Table([[p] for p in content_cell], colWidths=[W - 22*mm],
style=TableStyle([
("TOPPADDING", (0,0),(-1,-1), 1),
("BOTTOMPADDING", (0,0),(-1,-1), 1),
("LEFTPADDING", (0,0),(-1,-1), 0),
]))
row_tbl = Table([[num_cell, inner_tbl]], colWidths=[16*mm, W - 22*mm],
style=TableStyle([
("BACKGROUND", (0,0),(0,-1), C_STEP_BG),
("BACKGROUND", (1,0),(1,-1), C_WHITE),
("LINEBELOW", (0,0),(-1,-1), 0.5, C_STEP_BD),
("LINEBEFORE", (0,0),(0,-1), 3, C_ACCENT),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("LEFTPADDING", (0,0),(0,0), 4),
("LEFTPADDING", (1,0),(1,-1), 8),
("VALIGN", (0,0),(-1,-1), "TOP"),
]))
return row_tbl
# ── OBS Menu Diagram (ASCII-art style using ReportLab drawing) ────────────────
class OBSMenuDiagram(Flowable):
"""Draws a simplified annotated OBS settings sidebar diagram."""
def __init__(self, width=W, height=72*mm):
super().__init__()
self.width = width
self.height = height
def draw(self):
c = self.canv
w, h = self.width, self.height
# outer window frame
c.setStrokeColor(C_BLUE)
c.setFillColor(colors.HexColor("#f0f4ff"))
c.setLineWidth(1.2)
c.roundRect(0, 0, w, h, 6, stroke=1, fill=1)
# title bar
c.setFillColor(C_DARK)
c.roundRect(0, h - 12*mm, w, 12*mm, 6, stroke=0, fill=1)
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 10)
c.drawString(6*mm, h - 8*mm, "OBS Studio - Settings")
# sidebar
sidebar_w = 42*mm
c.setFillColor(colors.HexColor("#dce3f5"))
c.rect(0, 0, sidebar_w, h - 12*mm, stroke=0, fill=1)
tabs = [
("General", False, None),
("Stream", False, None),
("Output", True, "Step 2 - change to Advanced"),
("Audio", True, "Step 6 - set 48kHz / 160kbps"),
("Video", True, "Step 7 - resolution & FPS"),
("Hotkeys", False, None),
("Advanced", True, "Step 8 - Dynamic Bitrate"),
]
tab_h = (h - 12*mm) / len(tabs)
for i, (label, highlight, annotation) in enumerate(tabs):
y = h - 12*mm - (i + 1) * tab_h
if highlight:
c.setFillColor(C_BLUE)
c.rect(0, y, sidebar_w, tab_h, stroke=0, fill=1)
c.setFillColor(C_WHITE)
else:
c.setFillColor(colors.HexColor("#1c1c2e"))
c.setFont("Helvetica-Bold" if highlight else "Helvetica", 8.5)
c.drawString(5*mm, y + tab_h * 0.32, label)
if highlight and annotation:
# draw arrow + annotation to the right
ax = sidebar_w + 3*mm
ay = y + tab_h * 0.4
# arrow line
c.setStrokeColor(C_ACCENT)
c.setLineWidth(1.2)
c.line(ax, ay, ax + 8*mm, ay)
# arrowhead
c.setFillColor(C_ACCENT)
c.polygon([ax+8*mm, ay, ax+6*mm, ay+1.2*mm, ax+6*mm, ay-1.2*mm], fill=1, stroke=0)
# annotation text
c.setFillColor(C_ACCENT)
c.setFont("Helvetica", 7.5)
c.drawString(ax + 9*mm, ay - 1*mm, annotation)
# vertical divider
c.setStrokeColor(C_BORDER)
c.setLineWidth(0.5)
c.line(sidebar_w, 0, sidebar_w, h - 12*mm)
# content area label
c.setFillColor(C_GREY)
c.setFont("Helvetica", 8)
c.drawString(sidebar_w + 5*mm, h - 20*mm, "Settings panel appears here")
c.drawString(sidebar_w + 5*mm, h - 25*mm, "when you click a tab")
# legend
c.setFillColor(C_BLUE)
c.rect(sidebar_w + 5*mm, 4*mm, 3*mm, 3*mm, stroke=0, fill=1)
c.setFillColor(colors.HexColor("#1c1c2e"))
c.setFont("Helvetica", 7.5)
c.drawString(sidebar_w + 10*mm, 4.5*mm, "= tabs you need to configure")
# ── OBS Output Tab Diagram ─────────────────────────────────────────────────────
class OBSOutputDiagram(Flowable):
"""Mockup of the Output tab showing key fields."""
def __init__(self, width=W, height=60*mm):
super().__init__()
self.width = width
self.height = height
def _field(self, c, label, value, x, y, field_w=55*mm, highlight=False):
lw = 38*mm
fh = 7*mm
# label
c.setFillColor(colors.HexColor("#1c1c2e"))
c.setFont("Helvetica", 8)
c.drawRightString(x + lw - 2*mm, y + 2*mm, label + ":")
# field box
fc = colors.HexColor("#fff8e1") if highlight else C_WHITE
bc = C_ORANGE if highlight else C_BORDER
c.setFillColor(fc)
c.setStrokeColor(bc)
c.setLineWidth(1.2 if highlight else 0.5)
c.roundRect(x + lw, y, field_w, fh, 2, stroke=1, fill=1)
c.setFillColor(C_BLUE if highlight else colors.HexColor("#333"))
c.setFont("Helvetica-Bold" if highlight else "Helvetica", 8)
c.drawString(x + lw + 3*mm, y + 2*mm, value)
if highlight:
c.setFillColor(C_ORANGE)
c.setFont("Helvetica-Bold", 7)
c.drawString(x + lw + field_w + 2*mm, y + 2.5*mm, "<-- set this")
def draw(self):
c = self.canv
w, h = self.width, self.height
# panel bg
c.setFillColor(colors.HexColor("#f8faff"))
c.setStrokeColor(C_BORDER)
c.setLineWidth(0.8)
c.roundRect(0, 0, w, h, 5, stroke=1, fill=1)
# panel title
c.setFillColor(C_BLUE)
c.roundRect(0, h - 9*mm, w, 9*mm, 5, stroke=0, fill=1)
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 9)
c.drawString(5*mm, h - 6*mm, "Settings > Output (Output Mode: Advanced > Encoding tab)")
# output mode row (top of panel)
c.setFillColor(colors.HexColor("#1c1c2e"))
c.setFont("Helvetica-Bold", 8)
c.drawString(5*mm, h - 16*mm, "Output Mode:")
c.setFillColor(C_ACCENT)
c.roundRect(40*mm, h - 17.5*mm, 28*mm, 7*mm, 2, stroke=0, fill=1)
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 8)
c.drawString(42*mm, h - 15*mm, "Advanced <-- select this")
base_y = h - 30*mm
gap = 9*mm
lx = 5*mm
self._field(c, "Encoder", "NVENC H.264 / x264", lx, base_y, highlight=False)
self._field(c, "Rate Control", "CBR", lx, base_y - gap, highlight=True)
self._field(c, "Video Bitrate", "6000 Kbps", lx, base_y - 2*gap, highlight=True)
self._field(c, "Keyframe Interval", "2s", lx, base_y - 3*gap, highlight=True)
self._field(c, "Preset", "Quality", lx, base_y - 4*gap, highlight=False)
self._field(c, "Profile", "High", lx, base_y - 5*gap, highlight=False)
# ── OBS Audio Tab Diagram ──────────────────────────────────────────────────────
class OBSAudioDiagram(Flowable):
"""Mockup of Audio settings panel."""
def __init__(self, width=W, height=44*mm):
super().__init__()
self.width = width
self.height = height
def _field(self, c, label, value, x, y, field_w=50*mm, highlight=False):
lw = 42*mm
fh = 7*mm
c.setFillColor(colors.HexColor("#1c1c2e"))
c.setFont("Helvetica", 8)
c.drawRightString(x + lw - 2*mm, y + 2*mm, label + ":")
fc = colors.HexColor("#e8f5e9") if highlight else C_WHITE
bc = C_GREEN if highlight else C_BORDER
c.setFillColor(fc)
c.setStrokeColor(bc)
c.setLineWidth(1.2 if highlight else 0.5)
c.roundRect(x + lw, y, field_w, fh, 2, stroke=1, fill=1)
c.setFillColor(C_GREEN if highlight else colors.HexColor("#333"))
c.setFont("Helvetica-Bold" if highlight else "Helvetica", 8)
c.drawString(x + lw + 3*mm, y + 2*mm, value)
if highlight:
c.setFillColor(C_GREEN)
c.setFont("Helvetica-Bold", 7)
c.drawString(x + lw + field_w + 2*mm, y + 2.5*mm, "<-- set this")
def draw(self):
c = self.canv
w, h = self.width, self.height
c.setFillColor(colors.HexColor("#f8faff"))
c.setStrokeColor(C_BORDER)
c.setLineWidth(0.8)
c.roundRect(0, 0, w, h, 5, stroke=1, fill=1)
c.setFillColor(C_GREEN)
c.roundRect(0, h - 9*mm, w, 9*mm, 5, stroke=0, fill=1)
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 9)
c.drawString(5*mm, h - 6*mm, "Settings > Audio")
base_y = h - 20*mm
gap = 9*mm
lx = 5*mm
self._field(c, "Sample Rate", "48 kHz", lx, base_y, highlight=True)
self._field(c, "Channels", "Stereo", lx, base_y - gap, highlight=False)
self._field(c, "Desktop Audio Bitrate", "160 Kbps", lx, base_y - 2*gap,highlight=True)
self._field(c, "Mic / Aux Bitrate", "160 Kbps", lx, base_y - 3*gap,highlight=True)
# ── OBS Video Tab Diagram ──────────────────────────────────────────────────────
class OBSVideoDiagram(Flowable):
"""Mockup of Video settings panel."""
def __init__(self, width=W, height=44*mm):
super().__init__()
self.width = width
self.height = height
def _field(self, c, label, value, x, y, field_w=55*mm, highlight=False):
lw = 50*mm
fh = 7*mm
c.setFillColor(colors.HexColor("#1c1c2e"))
c.setFont("Helvetica", 8)
c.drawRightString(x + lw - 2*mm, y + 2*mm, label + ":")
fc = colors.HexColor("#e8eaf6") if highlight else C_WHITE
bc = C_PATH_BD if highlight else C_BORDER
c.setFillColor(fc)
c.setStrokeColor(bc)
c.setLineWidth(1.2 if highlight else 0.5)
c.roundRect(x + lw, y, field_w, fh, 2, stroke=1, fill=1)
c.setFillColor(C_PATH_BD if highlight else colors.HexColor("#333"))
c.setFont("Helvetica-Bold" if highlight else "Helvetica", 8)
c.drawString(x + lw + 3*mm, y + 2*mm, value)
if highlight:
c.setFillColor(C_PATH_BD)
c.setFont("Helvetica-Bold", 7)
c.drawString(x + lw + field_w + 2*mm, y + 2.5*mm, "<-- set this")
def draw(self):
c = self.canv
w, h = self.width, self.height
c.setFillColor(colors.HexColor("#f8faff"))
c.setStrokeColor(C_BORDER)
c.setLineWidth(0.8)
c.roundRect(0, 0, w, h, 5, stroke=1, fill=1)
c.setFillColor(C_PATH_BD)
c.roundRect(0, h - 9*mm, w, 9*mm, 5, stroke=0, fill=1)
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 9)
c.drawString(5*mm, h - 6*mm, "Settings > Video")
base_y = h - 20*mm
gap = 9*mm
lx = 5*mm
self._field(c, "Base (Canvas) Resolution", "1920x1080", lx, base_y, highlight=False)
self._field(c, "Output (Scaled) Resolution","1920x1080", lx, base_y - gap, highlight=True)
self._field(c, "Common FPS Values", "60 / 30", lx, base_y - 2*gap, highlight=True)
self._field(c, "Downscale Filter", "Lanczos", lx, base_y - 3*gap, highlight=True)
# ── Build story ───────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
rightMargin=MARGIN, leftMargin=MARGIN,
topMargin=14*mm, bottomMargin=14*mm,
title="OBS Studio Navigation Guide",
author="Orris AI"
)
story = []
# HEADER BANNER
banner = Table(
[[Paragraph("OBS Studio", title_s)],
[Paragraph("Step-by-Step Navigation Guide | Where to Find Every Setting | 2026", sub_s)]],
colWidths=[W],
style=TableStyle([
("BACKGROUND", (0,0),(-1,-1), C_DARK),
("TOPPADDING", (0,0),(-1,-1), 10),
("BOTTOMPADDING", (0,0),(-1,-1), 10),
("ALIGN", (0,0),(-1,-1), "CENTER"),
]))
story.append(banner)
story.append(Spacer(1, 5*mm))
# ── INTRO ──────────────────────────────────────────────────────────────────────
story.append(callout(
"How to open Settings in OBS: Click <b>Settings</b> button in the bottom-right "
"corner of the OBS main window (or press Ctrl + , on Windows / Cmd + , on Mac). "
"A panel opens with a sidebar on the left. Click each tab to configure it.",
note_s, C_NOTE_BG, C_NOTE_BD))
story.append(Spacer(1, 4*mm))
# ── SIDEBAR MAP ───────────────────────────────────────────────────────────────
story.append(KeepTogether([
sec_hdr("PART 1 - Settings Panel Layout (click the tabs shown in blue)"),
Spacer(1, 3*mm),
OBSMenuDiagram(width=W, height=72*mm),
Spacer(1, 2*mm),
callout(
"TIP: You only need to touch 4 tabs: Output, Audio, Video, and Advanced. "
"Leave all other tabs (General, Stream, Hotkeys) at their defaults unless you have a specific need.",
note_s, C_NOTE_BG, C_NOTE_BD),
Spacer(1, 5*mm),
]))
# ── STEP BY STEP ──────────────────────────────────────────────────────────────
story.append(sec_hdr("PART 2 - Step-by-Step Walkthrough"))
story.append(Spacer(1, 3*mm))
steps = [
("Open Settings",
"In the main OBS window, look at the bottom-right corner. Click the <b>Settings</b> button.",
None),
("Go to Output tab",
"In the left sidebar, click <b>Output</b>. At the very top of the panel you will see "
"<b>Output Mode</b> set to 'Simple'. Change this dropdown to <b>Advanced</b>. "
"More options will appear immediately below.",
"Output Mode = Advanced"),
("Select the Encoding tab",
"After switching to Advanced mode, you will see sub-tabs: <b>Streaming</b>, Recording, "
"Replay Buffer. Make sure <b>Streaming</b> is selected (it is the default).",
None),
("Set your Encoder",
"Find the <b>Encoder</b> dropdown. "
"NVIDIA GPU users: choose <b>NVENC H.264</b>. "
"AMD GPU users: choose <b>AMF H.264</b>. "
"No GPU / using CPU: choose <b>x264</b>.",
None),
("Set Rate Control to CBR",
"Find the <b>Rate Control</b> dropdown directly below Encoder. "
"Change it from 'CQP' or 'VBR' to <b>CBR</b> (Constant Bitrate). "
"This is required by Twitch, YouTube, and all major platforms.",
"Rate Control = CBR"),
("Set Video Bitrate",
"In the <b>Bitrate</b> field, type your bitrate: "
"1080p/30fps = 4500-6000 kbps | 1080p/60fps = 6000-9000 kbps | "
"720p = 2500-4500 kbps. "
"Match this to your platform cap (Twitch max = 6000).",
"Bitrate = 6000 (or per preset)"),
("Set Keyframe Interval",
"Find <b>Keyframe Interval</b>. It may default to 0 (auto). "
"Change it to <b>2</b>. This is mandatory for Twitch and YouTube - "
"leaving it at 0 can cause buffering for viewers.",
"Keyframe Interval = 2"),
("Set Preset and Profile",
"Set <b>Preset</b> to <b>Quality</b> (NVENC) or <b>medium</b> (x264). "
"Set <b>Profile</b> to <b>High</b>. Leave all other fields at their defaults.",
"Preset = Quality | Profile = High"),
("Click Apply, then go to Audio tab",
"Click <b>Apply</b> at the bottom to save Output settings. "
"Then click <b>Audio</b> in the left sidebar.",
None),
("Configure Audio settings",
"Set <b>Sample Rate</b> to <b>48 kHz</b>. Set <b>Channels</b> to <b>Stereo</b>. "
"Scroll down to find <b>Desktop Audio</b> and <b>Mic/Aux</b> bitrate dropdowns - "
"set both to <b>160 Kbps</b>. Click Apply.",
"Sample Rate=48kHz | Audio Bitrate=160 Kbps"),
("Go to Video tab",
"Click <b>Video</b> in the left sidebar. "
"Set <b>Output (Scaled) Resolution</b> to 1920x1080 (or 1280x720 for low upload). "
"Set <b>Common FPS Values</b> to 60 (gaming) or 30 (talk/IRL). "
"Set <b>Downscale Filter</b> to <b>Lanczos</b>. Click Apply.",
"Resolution=1920x1080 | FPS=60 | Filter=Lanczos"),
("Go to Advanced tab (optional but recommended)",
"Click <b>Advanced</b> in the sidebar. Find <b>Network</b> section. "
"Enable <b>Dynamically change bitrate to manage congestion</b>. "
"This automatically lowers bitrate during network spikes instead of dropping frames.",
"Dynamic Bitrate = Enabled"),
("Click OK and test",
"Click <b>OK</b> to close Settings. Go to <b>Settings > Stream</b> to connect your "
"platform account if you haven't already. Then click <b>Start Streaming</b>. "
"For first time: use a private/unlisted stream to verify quality before going live.",
None),
]
for i, (title, desc, value) in enumerate(steps, 1):
story.append(step_row(i, title, desc, value))
story.append(Spacer(1, 5*mm))
# ── ANNOTATED MOCKUPS ─────────────────────────────────────────────────────────
story.append(KeepTogether([
sec_hdr("PART 3 - Annotated Panel Mockups (orange = fields to change)"),
Spacer(1, 3*mm),
Paragraph("<b>Output Tab (Steps 2-8)</b>", bold_s),
Spacer(1, 2*mm),
OBSOutputDiagram(width=W, height=60*mm),
Spacer(1, 4*mm),
]))
story.append(KeepTogether([
Paragraph("<b>Audio Tab (Step 10) - green fields</b>", bold_s),
Spacer(1, 2*mm),
OBSAudioDiagram(width=W, height=44*mm),
Spacer(1, 4*mm),
]))
story.append(KeepTogether([
Paragraph("<b>Video Tab (Step 11) - blue fields</b>", bold_s),
Spacer(1, 2*mm),
OBSVideoDiagram(width=W, height=44*mm),
Spacer(1, 5*mm),
]))
# ── QUICK REFERENCE CHEATSHEET ────────────────────────────────────────────────
story.append(KeepTogether([
sec_hdr("PART 4 - Quick Reference Cheatsheet"),
Spacer(1, 3*mm),
make_table(
["Tab", "Field", "Set To"],
[
["Output > Streaming", "Output Mode", "Advanced"],
["Output > Streaming", "Rate Control", "CBR"],
["Output > Streaming", "Bitrate", "6000 Kbps (Twitch) / 8000 Kbps (YouTube)"],
["Output > Streaming", "Keyframe Interval", "2 seconds"],
["Output > Streaming", "Preset", "Quality (NVENC) / medium (x264)"],
["Output > Streaming", "Profile", "High"],
["Audio", "Sample Rate", "48 kHz"],
["Audio", "Desktop + Mic", "160 Kbps AAC"],
["Video", "Output Resolution", "1920x1080 (or 1280x720)"],
["Video", "FPS", "60 (gaming) / 30 (talk/IRL)"],
["Video", "Downscale Filter", "Lanczos"],
["Advanced", "Dynamic Bitrate", "Enabled"],
],
[42*mm, 52*mm, 86*mm]
),
Spacer(1, 4*mm),
]))
# ── COMMON MISTAKES ───────────────────────────────────────────────────────────
story.append(KeepTogether([
sec_hdr("PART 5 - Common Mistakes to Avoid", color=colors.HexColor("#7b1a1a")),
Spacer(1, 3*mm),
make_table(
["Mistake", "What Happens", "Fix"],
[
["Leaving Output Mode on 'Simple'", "You miss CBR, keyframe, and profile settings", "Switch to Advanced"],
["Keyframe Interval left at 0 (auto)", "Viewer buffering / bad segments on Twitch", "Set to 2"],
["Bitrate exceeds platform cap", "Stream gets dropped or rejected", "Stay at or below cap"],
["Rate Control set to VBR", "Platform ingest server rejects the stream", "Switch to CBR"],
["Audio sample rate set to 44.1 kHz", "Audio sync drift over long streams", "Set to 48 kHz"],
["No mic filters applied", "Echo, background noise, clipping for viewers","Add Noise Suppression + Gate + Compressor"],
],
[50*mm, 62*mm, 68*mm]
),
Spacer(1, 4*mm),
]))
# ── FOOTER ────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width=W, thickness=0.5, color=C_BORDER))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"Generated by Orris AI | OBS Studio 2026 | obsproject.com",
small_s))
doc.build(story)
print(f"PDF created: {OUTPUT}")
python /tmp/workspace/obs-settings/generate_nav_guide.py
~/obs-settings/generate_nav_guide.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/obs-settings/generate_nav_guide.py'
python /tmp/workspace/obs-settings/generate_nav_guide.py
OBS Navigation Guide
PDF Document · PDF