A Python library for creating videos that simulate typing code, with a smooth camera that follows the cursor.
Note: This is a reimplementation of the original CodeVideoRenderer and PypiProject by ZhuChongjing. The original uses
manimas its rendering engine, which requires Microsoft C++ Build Tools to install on Windows. This version replacesmanimentirely withPillow+moviepy, so it works out of the box on any Python 3.8+ environment without any C++ compiler.
| Feature | Original | This version |
|---|---|---|
| Rendering engine | manim | Pillow + moviepy |
| Requires C++ Build Tools | Yes | No |
| Camera follow | Smooth X+Y via manim scene | Smooth X+Y via lerp per frame |
| Cursor blink | Yes | Yes |
| Glow effect | moviepy | moviepy (same) |
reverse_typing |
No | Yes |
| Custom font | Yes (fonts/ folder) |
Yes (fonts/ folder) |
| Line numbers | Yes | Yes |
center layout |
No | Yes |
Known limitations compared to the original:
- No OpenGL renderer option (Cairo only in original, here it's Pillow)
- No random typing interval variation per character (use
interval_range=(0.08, 0.15)for variation — min/max are applied uniformly, not per-character randomly) - Camera follows cursor but does not do the subtle vertical oscillation the original does while typing a long line
formatter_styleonly supports pygments built-in styles — custom VS Code themes require passing color dicts manually
Pillow>=9.0
pygments>=2.10
moviepy==1.0.3
rich>=13.0
proglog
Install:
pip install Pillow pygments "moviepy==1.0.3" rich proglogimport sys
sys.path.insert(0, '/path/to/folder') # folder containing CVRer/
from CVRer import CameraFollowCursorCV
cv = CameraFollowCursorCV(
code=('string', """
def hello():
name = "World"
print(f"Hello, {name}!")
return name
result = hello()
print(result)
"""),
language='python',
formatter_style='github-dark',
)
cv.render()Output: CameraFollowCursorCV.mp4 in the current directory.
Source code to animate. Two formats:
# From a string
code=('string', 'print("hello")')
# From a file
code=('file', 'path/to/script.py')The file must be UTF-8 encoded. Characters \r, \v, \f are not allowed.
Pygments language alias. Examples:
language='python'
language='html'
language='javascript'
language='typescript'
language='cpp'
language='rust'
language='go'
language='sql'
language='bash'
language='json'
language='yaml'Full list: run from pygments.lexers import get_all_lexers; print(list(get_all_lexers())) or see CVRer/typing.py.
Syntax highlight color theme. Must be a valid pygments style name.
formatter_style='github-dark' # dark, GitHub-style
formatter_style='monokai' # classic dark
formatter_style='dracula' # purple dark
formatter_style='one-dark' # Atom One Dark
formatter_style='nord' # Nordic blue-grey
formatter_style='gruvbox-dark' # warm retro dark
formatter_style='solarized-dark' # Solarized
formatter_style='vs' # light, Visual Studio-like
formatter_style='xcode' # light, Xcode-likeFull list:
from pygments.styles import get_all_styles
print(list(get_all_styles()))Font size in pixels. The library first looks for a .ttf file in CVRer/fonts/, then falls back to system fonts (Consolas, Courier New, Lucida Console).
font_size=20 # smaller, more code fits on screen
font_size=26 # default
font_size=32 # larger, easier to readTo use a custom font — place any .ttf file into CVRer/fonts/. It will be picked up automatically.
Line height multiplier relative to the font's character height.
line_spacing=1.2 # compact
line_spacing=1.5 # default, comfortable
line_spacing=2.0 # spaciousTyping speed as (min_seconds, max_seconds) per character. This also determines the video FPS: fps = round(1 / min_seconds).
interval_range=(0.15, 0.15) # 0.15s per char, ~7 fps — slow, readable
interval_range=(0.08, 0.08) # 0.08s per char, ~12 fps — medium
interval_range=(0.05, 0.05) # 0.05s per char, ~20 fps — fast
interval_range=(0.03, 0.03) # 0.03s per char, ~33 fps — very fastCurrently both values must be equal. Per-character random variation is not yet implemented.
Output video resolution scale.
camera_scale=1.0 # 1920x1080 (Full HD)
camera_scale=0.5 # 960x540
camera_scale=0.75 # 1440x810How close the camera is to the code. Higher = more zoomed in, fewer characters visible at once.
camera_zoom=1.0 # wide view, lots of code visible
camera_zoom=1.5 # slightly zoomed
camera_zoom=2.0 # default, comfortable close-up
camera_zoom=3.0 # very close, ~20 chars per line visibleInternally this crops a smaller region of the canvas and upscales it to the output resolution.
Camera movement smoothing coefficient per frame. Range: 0.0 to 1.0.
camera_smooth=0.05 # very slow, cinematic drift
camera_smooth=0.15 # smooth, natural
camera_smooth=0.25 # default
camera_smooth=0.5 # snappy, follows cursor closely
camera_smooth=1.0 # instant, no smoothingSeconds the video holds after all characters are typed. During this time the camera continues smoothly settling and the cursor keeps blinking.
end_pause=0.5 # quick cut
end_pause=2.0 # default
end_pause=5.0 # long holdWhether to center the code block within the viewport. When True, the code appears in the middle of the frame rather than top-left.
center=True # code centered in frame
center=False # code starts at top-leftCursor blink period in seconds. The cursor is visible for half the period, hidden for the other half. Set to 0 to disable blinking.
cursor_blink_rate=0.5 # default, blinks every 0.5s
cursor_blink_rate=1.0 # slow blink
cursor_blink_rate=0.3 # fast blink
cursor_blink_rate=0 # no blinking, always visibleOutput file name without the .mp4 extension. Can be an absolute path.
video_name='my_video' # saves as my_video.mp4 in cwd
video_name='C:/Users/User/Desktop/output' # absolute path
video_name='renders/python_tutorial' # relative subfolderShow line numbers on the left side.
line_numbers=True # show line numbers
line_numbers=False # hide line numbersApply a glow/bloom post-processing effect after rendering. Uses moviepy to process each frame with Gaussian blur + brightness boost. Adds noticeable processing time.
glow=True # apply glow (slower, looks better)
glow=False # skip glow (faster)cv.render(
output=True, # print progress to console
reverse_typing=False, # erase code after end_pause
)Print rendering progress, fps, resolution info and timing to the console.
After the end_pause, erase all typed characters one by one in reverse order until the screen is completely empty. The cursor keeps blinking during erasure.
cv.render(reverse_typing=True)import sys
sys.path.insert(0, 'C:/Users/User/Desktop/MPCode')
from CVRer import CameraFollowCursorCV
cv = CameraFollowCursorCV(
code=('string', """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- Code -->
</body>
</html>
"""),
language='html',
formatter_style='github-dark',
font_size=26,
line_spacing=1.5,
interval_range=(0.08, 0.08),
camera_scale=1.0,
camera_zoom=2.0,
camera_smooth=0.15,
end_pause=3.0,
center=True,
cursor_blink_rate=0.5,
video_name='C:/Users/User/Desktop/output',
line_numbers=True,
glow=True,
)
cv.render(output=True, reverse_typing=False)cv = CameraFollowCursorCV(
code=('file', 'C:/path/to/folder/script.py'),
language='python',
formatter_style='monokai',
video_name='C:/Users/User/Desktop/script_video',
)
cv.render()from pygments.styles import get_all_styles
print(sorted(get_all_styles()))
# ['abap', 'algol', 'arduino', 'autumn', 'dracula', 'emacs',
# 'friendly', 'fruity', 'github-dark', 'gruvbox-dark', 'gruvbox-light',
# 'inkpot', 'material', 'monokai', 'native', 'nord', 'nord-darker',
# 'one-dark', 'paraiso-dark', 'solarized-dark', 'solarized-light',
# 'tango', 'vim', 'vs', 'xcode', 'zenburn', ...]