Skip to content

Instantly share code, notes, and snippets.

@darfink
Created February 19, 2026 09:19
Show Gist options
  • Select an option

  • Save darfink/0840c75173a1c98c5260838db7d65818 to your computer and use it in GitHub Desktop.

Select an option

Save darfink/0840c75173a1c98c5260838db7d65818 to your computer and use it in GitHub Desktop.
A script to visualize frames types in a video file using ffprobe
#!/usr/bin/env python3
import argparse
import json
import shutil
import subprocess
import sys
from dataclasses import dataclass
RESET = "\x1b[0m"
BOLD = "\x1b[1m"
COLORS = {
"I": "\x1b[1;32m", # bold green
"i": "\x1b[35m", # magenta
"P": "\x1b[36m", # cyan
"B": "\x1b[33m", # yellow
"?": "\x1b[90m", # gray
}
INDEX_COLOR = "\x1b[90m" # dim gray
TS_COLOR = "\x1b[34m" # blue
def supports_color(mode: str) -> bool:
if mode == "always":
return True
if mode == "never":
return False
return sys.stdout.isatty()
def colorize(ch: str, use_color: bool) -> str:
if not use_color or ch == " ":
return ch
return f"{COLORS.get(ch, COLORS['?'])}{ch}{RESET}"
def style(text: str, use_color: bool, color: str = "", bold: bool = False) -> str:
if not use_color and not bold:
return text
parts = []
if bold:
parts.append(BOLD)
if use_color and color:
parts.append(color)
if not parts:
return text
return "".join(parts) + text + RESET
@dataclass
class Frame:
idx: int
typ: str
key: int
pts: str
def ffprobe_frames(path: str, stream: str, offset: float, duration: float):
interval = f"{max(0.0, offset)}%+{duration}" if duration > 0 else f"{max(0.0, offset)}%"
cmd = [
"ffprobe",
"-v",
"error",
"-read_intervals",
interval,
"-select_streams",
stream,
"-show_entries",
"frame=pict_type,key_frame,best_effort_timestamp_time",
"-of",
"json",
path,
]
proc = subprocess.run(cmd, capture_output=True, text=True)
if proc.returncode != 0:
raise RuntimeError((proc.stderr or "").strip() or "ffprobe failed")
try:
payload = json.loads(proc.stdout or "{}")
except Exception as e:
raise RuntimeError(f"invalid ffprobe json: {e}")
for idx, frame in enumerate(payload.get("frames", [])):
typ = str(frame.get("pict_type", "?") or "?")
try:
key = int(frame.get("key_frame", 0))
except ValueError:
key = 0
pts = str(frame.get("best_effort_timestamp_time", ""))
yield Frame(idx=idx, typ=typ, key=key, pts=pts)
def normalize_type(f: Frame, mark_nonkey_i: bool) -> str:
if mark_nonkey_i and f.typ == "I" and f.key == 0:
return "i"
return f.typ
def detect_ts_precision(frames) -> int:
max_dec = 0
for f in frames:
pts = (f.pts or "").strip()
if not pts or "." not in pts:
continue
frac = pts.split(".", 1)[1].rstrip()
max_dec = max(max_dec, len(frac))
return max_dec
def format_ts(pts: str, decimals: int) -> str:
if not pts:
return ""
try:
val = float(pts)
except ValueError:
return pts
if decimals <= 0:
return str(int(round(val)))
return f"{val:.{decimals}f}"
def print_grid(rows, width: int, use_color: bool, ts_decimals: int):
if width <= 0:
width = 64
group = 8
sep_every = group
ts_w = max(2, ts_decimals + 2)
frame_w = width + (width - 1) // sep_every
def fmt_frames(text: str) -> str:
chunk = text[:width]
out = []
for i in range(width):
ch = chunk[i] if i < len(chunk) else " "
out.append(colorize(ch, use_color))
if (i + 1) % sep_every == 0 and i + 1 < width:
out.append(" ")
return "".join(out)
top = f"┌{'─'*8}┬{'─'*ts_w}┬─{'─'*frame_w}─┐"
idx_h = style(f"{'index':>8}", use_color, INDEX_COLOR, bold=True)
ts_h = style(f"{'ts':>{ts_w}}", use_color, TS_COLOR, bold=True)
fr_h = style(f"{'frames'.ljust(frame_w)}", use_color, "", bold=True)
head = f"│{idx_h}│{ts_h}│ {fr_h} │"
sep = f"├{'─'*8}┼{'─'*ts_w}┼─{'─'*frame_w}─┤"
row_sep = f"├{'╌'*8}┼{'╌'*ts_w}┼─{'╌'*frame_w}─┤"
bot = f"└{'─'*8}┴{'─'*ts_w}┴─{'─'*frame_w}─┘"
print(top)
print(head)
print(sep)
for row_i, (idx, ts, framestr) in enumerate(rows):
for offset in range(0, len(framestr), width):
part = framestr[offset:offset + width]
idx_raw = f"{idx:08d}" if offset == 0 else " " * 8
ts_fmt = format_ts(ts, ts_decimals)
ts_raw = (ts_fmt[:ts_w].rjust(ts_w)) if offset == 0 else " " * ts_w
idx_cell = style(idx_raw, use_color, INDEX_COLOR)
ts_cell = style(ts_raw, use_color, TS_COLOR)
print(f"│{idx_cell}│{ts_cell}│ {fmt_frames(part)} │")
if row_i + 1 < len(rows):
print(row_sep)
print(bot)
def build_idr_rows(frames, seq):
key_i_positions = [i for i, f in enumerate(frames) if f.typ == "I" and f.key == 1]
if not seq:
return []
rows = []
if not key_i_positions:
rows.append((frames[0].idx if frames else 0, frames[0].pts if frames else "", "".join(seq)))
return rows
for i, start in enumerate(key_i_positions):
end = key_i_positions[i + 1] if i + 1 < len(key_i_positions) else len(seq)
rows.append((frames[start].idx, frames[start].pts if start < len(frames) else "", "".join(seq[start:end])))
return rows
def parse_args():
p = argparse.ArgumentParser(description="Inspect video GOP rows (one row per key I frame)")
p.add_argument("input", help="Input video file")
p.add_argument("-s", "--stream", default="v:0", help="ffprobe stream selector (default: v:0)")
p.add_argument("-w", "--width", type=int, default=64, help="Frames column width (default: 64)")
p.add_argument("-t", "--time", type=float, default=60.0, help="Time span in seconds to inspect (default: 60)")
p.add_argument("-o", "--offset", type=float, default=0.0, help="Start offset in seconds (default: 0)")
p.add_argument("--no-nonkey-i", action="store_true", help="Do not lowercase non-key I-frames")
p.add_argument("--color", choices=["auto", "always", "never"], default="auto", help="Color output")
return p.parse_args()
def main() -> int:
args = parse_args()
if shutil.which("ffprobe") is None:
print("vidframes: ffprobe not found", file=sys.stderr)
return 127
try:
frames = list(ffprobe_frames(args.input, args.stream, args.offset, args.time))
except RuntimeError as e:
print(f"vidframes: {e}", file=sys.stderr)
return 1
mark_nonkey_i = not args.no_nonkey_i
seq = [normalize_type(f, mark_nonkey_i) for f in frames]
rows = build_idr_rows(frames, seq)
ts_decimals = detect_ts_precision(frames)
print_grid(rows, args.width, supports_color(args.color), ts_decimals)
return 0
if __name__ == "__main__":
sys.exit(main())
@darfink
Copy link
Author

darfink commented Feb 19, 2026

Example output (has colors in CLI):

❯ vidframes -t 60 big-buck-bunny.mp4
┌────────┬────────┬─────────────────────────────────────────────────────────────────────────┐
│   index│      ts│ frames                                                                  │
├────────┼────────┼─────────────────────────────────────────────────────────────────────────┤
│00000000│0.000000│ IPPPiPBB PBBPPBPP BBPBPBPB PBPBPBPB BPPBPBPB PBBPPBPB PBBPBBPB BPBBPBBP │
│        │        │ BPBBPBBP PBPBPBPB PBPPBPBP PPBPPBPB PBPBPBPB PBPBBPPB BPBBPBPB PBPPBPBP │
│        │        │ BBPBBPBP BPBBPBPB PBPBBPBB PBBPBBPB BPPPPBPP PPPPPPPP PPPPBPBP PPPBPPPP │
│        │        │ PPBBPBBP BPBBPBBP BBPBPBBP BBPBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BPBP     │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00000252│4.200000│ IPPPPPPP PPPPBPPB PBPBPBBP BBPBBPBB PBBPPPPP PPPPPPPP                   │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00000300│5.000000│ IPPPPPPP PPPBBPPB BPBBPBPB PBPBPBBP BBPBBPBP BPBPBPBB PBBPBBPB BPBBPBPB │
│        │        │ PBPBPBPB BPBPBPPP PPPPPBPB PBPBPBPB PBPBPBBP BBPBBPBB PBBPBBPB BPBBPBBP │
│        │        │ BPBPBPBP BPBPBBPP BPBPBPBP BPBPBBPB BPBBPBPB PBPBPBPB PBPBBPBB PBBPBBPB │
│        │        │ BPBBPBBP BPBPBPBP BPBPBPBP BPBPBPBP BPBPBPBP BPBPBPBB PBPBPBBP BPBPBPBP │
│        │        │ BPBPBPBP BPBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB PBBP                       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00000600│10.00000│ IBBPBBPB BPBBPBBP BPBPBBPB BPBBPBPB PBPBPBPB PBBPBBPP PPPPPPPP PBPBPBBP │
│        │        │ BPBPBBPB BPBBPBBP BBPBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB │
│        │        │ BPBBPBBP BBPBPBPB PBPBPBPB PBBPBPPP PPPPPPBP PPBPPPPP PPPBBPBB PPPBPBPB │
│        │        │ BPBBPBBP BPBBPBPB PBPBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBPBBP BPBPBPBP │
│        │        │ BBPBBPBP BPBPBPBP BBPBPBBP BBPBBPBB PBBPBBPB PBBP                       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00000900│15.00000│ IBPBBPBP BPBPBPBP BPBBPBBP BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBPB PBPPBPPP │
│        │        │ PPPPPPPP PPPP                                                           │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00000976│16.26666│ IBBPPPPP PPPPPBBP PPPPPPPP PPPPBBPP PPPPBBPP PPBBPPPP PPPPPPPP PPBBPPPP │
│        │        │ PPBBPPPP PBBPPPPP PPPPBBPP BBPPPPPP BBPPPPPB BPPPPPPP PPBBPPBB PPPPPPPP │
│        │        │ PPPPPBBP PPPPPPPP PPPPBBPP PPPPPPPP BBPBBPBB PPPPPPPP PPBBPPPP BBPPPPBB │
│        │        │ PBBPBBPP PPBBPPPP BBPPPPPP PPPPBBPP                                     │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00001200│20.00000│ IPBBPPPP iBPPPPBB PBPBPBPB PBPBBPBP BPBPBBPB PBBPBPBP PPPBPBPB PBPBPBBP │
│        │        │ BPBBPBPB BPBBPBBP BBPBPBPB PBBPBBPB BPBBPBBP BPPBPBBP BPBPBBPB PBPBPBPB │
│        │        │ BPBBPBBP BBPBBPBB PBPPBPBP BPBPBBPB BPBPBBPB BPBPBPBB PBBPBPBB PBPBPBPB │
│        │        │ PBBPBBPB BPBBPBPB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBPB PBPBPPBP │
│        │        │ BPPPPBBP BPPBBPBP BBPBPBBP BPBBPPPB PBPBPBPB PPBP                       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00001500│25.00000│ IBPBPBPB PBPBPPPP PPBPBPPB BPBBPBBP BPBPBPBP BPBPBBPB BPBPBPBB PBPBBPBP │
│        │        │ BBPBPBPB PBPBPPBP BPPBPBPB PBBPBPBP BPBPPPBP BBPBPBPB PBPBPBPB BPBPBPPP │
│        │        │ PBBPBPBB PBBPBBPB PP                                                    │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00001646│27.43333│ IPPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB │
│        │        │ BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB │
│        │        │ PBBPBBPB BPBBPBBP PBBPBBPB BP                                           │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00001800│30.00000│ IBBPBBPB BPBBPPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB │
│        │        │ PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP │
│        │        │ BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB │
│        │        │ BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB │
│        │        │ PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP PBBP                       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00002100│35.00000│ IBBPBBPP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB │
│        │        │ PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP │
│        │        │ BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB │
│        │        │ BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB │
│        │        │ PBBPBBPB BPBBPBBP BBPBBPBB PBBP                                         │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00002384│39.73333│ IBBPBBPB BPBBPBBP                                                       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00002400│40.00000│ IBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP │
│        │        │ BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB │
│        │        │ BPBBPBBP BBPBPBBP BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB │
│        │        │ BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB │
│        │        │ PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBPB PBBP                       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00002700│45.00000│ IBBPBBPB BPBBPBBP BBPBPBBP BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB │
│        │        │ PBBPBBPB BPBBPBPB BPBBPBPB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPPBBPBB │
│        │        │ PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPPBBP PBBPBBPB │
│        │        │ BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB │
│        │        │ PBPBBPBB PBBPBBPB PBBPBBPB BPBBPBBP BBPBBPBB PBBP                       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00003000│50.00000│ IBBPBBPB BPBPBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBPBB PBBPBBPB BPBBPBPB │
│        │        │ PBPBPBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPPPPPP PBPBPBPB BPPBPP   │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00003126│52.10000│ IBPPPPPP BBPPPPPP PPPBBPPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBPB PBPBBPBP │
│        │        │ BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB BPBBPBBP BBPBBPBB PBBPBBPB │
│        │        │ BPBPBPBB PBBPBPBB PBPBBPBB PBBPBBPB BPBBPBBP BBPBBP                     │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼─╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌─┤
│00003300│55.00000│ IBBPBBPB BPPBBPBB PBPPBBPP PPPPPBBP PPPPPPBP PBPPBBPB PPBPBPBP BPPBBPPB │
│        │        │ PPPPPPPP BPBPPPPP PPPPPPPP PBBPBPPP PPPPPPPP BBPPPPPP PPPPPPPB BPPPPPPP │
│        │        │ PPPPPPPB PPPPPPPP PPPPPBBP PPPPPPPP PPPPBBPP PPPPPPPP PPPPPPPP PPPPPPPP │
│        │        │ PPBBPPPP PPPPPPPP PPBPPPPP PPPPPPPP PBPPPPPP PPPPPPPP BPPPPPPP PPPPPPBB │
│        │        │ PPPPBPBP BPPPPBBP BPBPBPBP BPPPBBPB PBPBPBPP PPPP                       │
└────────┴────────┴─────────────────────────────────────────────────────────────────────────┘

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment