Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 5 additions & 27 deletions CodeVideoRenderer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,9 @@
"""
CodeVideoRenderer – A Python animation library for creating dynamic code demonstration videos.

This package provides tools to transform static code into lively animations that simulate
real programming processes, built on top of the Manim engine.

Quick start
-----------

>>> from CodeVideoRenderer import CameraFollowCursorCV
>>> video = CameraFollowCursorCV(
... code=('string', 'print("Hello, World!")'),
... language='python',
... video_name='HelloWorld'
... )
>>> video.render()

Modules
-------

* :mod:`~.renderer` — Core rendering engine (:class:`~.CameraFollowCursorCV`).
* :mod:`~.config` — Default constants and configuration values.
* :mod:`~.typing` — Type aliases used across the library.
* :mod:`~.utils` — Internal utility functions and helpers.
* :mod:`~.version` — Package version string.
"""
"""Code video rendering built on Manim: animate code being typed while the camera follows the cursor."""
from .renderer import *
from .config import *
from .typing import *
from .utils import *
from .version import __version__
from .postprocess import *
from .vscode_theme import VSCodeDarkPlusStyle, register_vscode, STYLE_NAME
from .ime import is_cjk, cjk_run_at, get_ime
from .version import __version__
34 changes: 12 additions & 22 deletions CodeVideoRenderer/config.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,33 @@
import os
import sys

from manim import config
from rich.console import Console
import sys

# 原始标准输出和标准错误流
ORIGINAL_STDOUT = sys.stdout
"""Original standard output stream, used for console output."""
ORIGINAL_STDERR = sys.stderr
"""Original standard error stream, used for console error output."""
ORIGINAL_PROGRESS_BAR = config.progress_bar
"""Original progress bar, used for progress tracking."""

# 默认设置
DEFAULT_OUTPUT_VALUE = True
"""Default output value."""
DEFAULT_LINE_SPACING = 0.8
"""Default line spacing."""
DEFAULT_CURSOR_HEIGHT = 0.35
"""Default cursor height."""
DEFAULT_CURSOR_WIDTH = 1e-4
"""Default cursor width."""
DEFAULT_CODE_FONT = 'Consolas'
"""Default code font."""
DEFAULT_CURSOR_TO_CHAR_BUFFER = 0.07
"""Default buffer time between cursor movement and character rendering."""
DEFAULT_TYPE_INTERVAL = 0.15
"""Default typing interval."""
DEFAULT_LINE_BREAK_RUN_TIME = 0.4
"""Default line break animation run time."""
DEFAULT_TAB_WIDTH = 4
"""Default tab width."""
DEFAULT_OUTPUT_CONSOLE = Console(file=ORIGINAL_STDOUT)
"""Default output console."""
DEFAULT_CURSOR_BLINK_RUN_TIME = 0.5
"""Default cursor blink animation run time."""

# 其他设置
# ffmpeg encode tuning: auto threads, fast preset, small buffer keeps memory low
FFMPEG_THREADS = os.cpu_count() or 4
FFMPEG_PRESET = "fast"
FFMPEG_BUFSIZE = "512k"

CODE_OFFSET = 0.08
"""Default offset for code rendering."""
NOT_AVAILABLE_CHARACTERS = '\r\v\f'
"""Characters that are not available for rendering."""
OCCUPY_CHARACTER = '('
"""Character that is used to occupy a position in the code."""

__all__ = [
"ORIGINAL_STDOUT",
Expand All @@ -57,7 +44,10 @@
"DEFAULT_TAB_WIDTH",
"DEFAULT_OUTPUT_CONSOLE",
"DEFAULT_CURSOR_BLINK_RUN_TIME",
"FFMPEG_THREADS",
"FFMPEG_PRESET",
"FFMPEG_BUFSIZE",
"CODE_OFFSET",
"NOT_AVAILABLE_CHARACTERS",
"OCCUPY_CHARACTER"
]
]
Binary file added CodeVideoRenderer/icons/function.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added CodeVideoRenderer/icons/variable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 95 additions & 0 deletions CodeVideoRenderer/ime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Chinese IME candidate box: pinyin above, a divider, then candidate words."""
try:
from pypinyin import pinyin as _py, Style as _Style
_HAS_PYPINYIN = True
except ImportError: # pragma: no cover - fall back to the character itself when pypinyin is missing
_HAS_PYPINYIN = False

__all__ = ["is_cjk", "cjk_run_at", "get_ime"]

# homophone candidates so the box looks real (pinyin -> candidate list)
_CANDIDATES = {
# single characters
"ni": ["你", "泥", "拟", "尼", "逆"],
"hao": ["好", "号", "浩", "耗", "毫"],
"shi": ["世", "是", "事", "试", "市"],
"jie": ["界", "借", "接", "街", "解"],
"da": ["打", "大", "答", "达", "搭"],
"wan": ["完", "玩", "碗", "晚", "万"],
"dai": ["代", "带", "待", "戴", "贷"],
"ma": ["码", "马", "妈", "吗", "麻"],
"zi": ["自", "字", "资", "紫", "子"],
"dong": ["动", "东", "懂", "冬", "洞"],
"qing": ["清", "请", "情", "轻", "青"],
"ping": ["屏", "平", "评", "苹", "凭"],
"zhong": ["中", "种", "重", "终", "众"],
"wen": ["文", "问", "闻", "温", "稳"],
"yan": ["演", "眼", "盐", "严", "言"],
"hou": ["后", "候", "厚", "喉", "猴"],
"hui": ["会", "回", "惠", "汇", "灰"],
"qi": ["契", "气", "起", "器", "期"],
"shu": ["数", "书", "树", "输", "术"],
"fei": ["斐", "飞", "非", "肥", "费"],
"bo": ["波", "播", "拨", "玻", "伯"],
"na": ["那", "哪", "拿", "纳", "呐"],
"di": ["第", "地", "的", "底", "弟"],
"ge": ["个", "各", "歌", "格", "哥"],
# two-character words (joined full pinyin)
"nihao": ["你好", "你", "泥", "拟", "尼"],
"shijie": ["世界", "试解", "时间", "四季", "师姐"],
"daima": ["代码", "大马", "打码", "大妈"],
"dawan": ["打完", "大碗", "答万"],
"yanshi": ["演示", "严实", "延时", "验尸"],
"zhongwen": ["中文", "种闻", "重文"],
"zidong": ["自动", "字动", "资东"],
"qingping": ["清屏", "轻评"],
"houqi": ["后期", "厚起"],
"chuli": ["处理", "除理", "矗立"],
"feibo": ["斐波", "飞播"],
"naqi": ["那契", "纳气"],
"shuru": ["输入", "书入", "数如"],
"hanzi": ["汉字", "含字"],
"wancheng": ["完成", "碗城"],
"chengxu": ["程序", "成序", "城需"],
}


def is_cjk(ch):
return len(ch) == 1 and "一" <= ch <= "鿿"


def cjk_run_at(text, index):
if not (0 <= index < len(text)) or not is_cjk(text[index]):
return index, index
start = index
while start > 0 and is_cjk(text[start - 1]):
start -= 1
end = index + 1
while end < len(text) and is_cjk(text[end]):
end += 1
return start, end


def _char_pinyin(ch):
if not _HAS_PYPINYIN:
return ch
try:
parts = _py(ch, style=_Style.NORMAL, errors=lambda x: [x])
return (parts[0][0] if parts and parts[0] else ch).lower()
except Exception:
return ch


def get_ime(text, index):
start, end = cjk_run_at(text, index)
if start >= end:
return "", []
word = text[start:start + 2]
pinyin = "-".join(_char_pinyin(c) for c in word)
key = pinyin.replace("-", "").lower()
candidates = _CANDIDATES.get(key)
if not candidates:
candidates = [word]
elif word not in candidates:
candidates = [word] + list(candidates)
return pinyin, candidates
Loading