P        δΔ\forall \exist \therefore \because \blacksquare \wedge \vert \Vert \parallel \nparallel \perp \cup \cap \to \implies \prec \succ \equiv \sim \simeq \cong \nsim \empty \mathcal{P} \iff \implies \subset \supset \ll \gg \mapsto \partial \delta \Delta \nabla \oint

ManimReveal.js + Typed.js를 장기 생산 관점에서 정밀 비교.

기준

  1. 5분 내외 롱폼 영상에 적합한 구조적 전개
  2. LaTeX 수식 중심 콘텐츠의 안정성
  3. Markdown 자산 재사용성
  4. 장기적 생산 속도와 유지보수 비용
  5. “옛날 판서 느낌을 디지털로 깔끔하게” 재현 가능성

1. Manim (Mathematical Animation Engine, 매니메이션 엔진)

핵심 성격

  • Python 기반 장면(Scene) 단위 애니메이션 엔진
  • LaTeX를 렌더링의 1급 시민으로 취급
  • 영상 자체를 목표 산출물로 설계됨

생산 파이프라인

plain text
Markdown/LaTeX 개념 정리
→ Manim Scene 스크립트
→ MP4 렌더

장점 (롱폼 + 장기 생산 관점)

  1. 수식 안정성
  2. 판서 느낌 구현 가능성
  3. 영상 품질 일관성
  4. 장기적 확장성

단점 (생산 속도 관점)

  1. 초기 비용
  2. Markdown 직접 호환성 부족

2. Reveal.js + Typed.js

핵심 성격

  • 슬라이드 중심 웹 프레젠테이션
  • Typed.js로 타이핑 효과 추가
  • MathJax로 LaTeX 렌더링

생산 파이프라인

plain text
Markdown
→ Reveal.js 슬라이드
→ 브라우저 렌더
→ 화면 녹화 or Puppeteer 캡처

장점 (생산 속도 관점)

  1. Markdown 친화성
  2. 초기 생산 속도
  3. 수정 비용 낮음

단점 (롱폼 영상 관점)

  1. 수식 애니메이션 한계
  2. 5분 영상의 구조적 피로
  3. 최종 영상 품질

핵심 비교 요약

항목 Manim Reveal.js + Typed.js
LaTeX 수식 안정성 매우 높음 중간
판서 느낌 구현 가능 (정밀) 제한적
Markdown 재사용 낮음 (직접 파이프라인 필요) 매우 높음
초기 생산 속도 느림 매우 빠름
장기 생산 속도 빠름 (모듈화 후) 중간
5분 롱폼 적합성 매우 높음 보통
영상 품질 렌더 기반, 안정 녹화 기반, 변수 많음

장기 생산 기준의 결론

  • “지금은 내용이 이미 다 있고, 앞으로 계속 만들 것”
  • “5분 롱폼 영상”
  • “수식이 주인공이고 판서 느낌이 핵심”

이 조건에서는 Manim이 장기적으로 더 빠른 도구가 된다.

초기에는 Reveal.js가 빨라 보이지만, 10편 이상부터는 Manim의 Scene 재사용성이 압도적으로 유리하다.

현실적인 권장 전략 (혼합)

  • 초반 1–2편:
  • 이후:

원하면 다음 단계로

  • “Markdown → Manim 반자동 파이프라인 설계”
  • “판서 느낌을 위한 Manim 애니메이션 템플릿” 까지 구체화해줄 수 있다.

최상의 결과물 기준

  1. Markdown(마크다운) 원본을 단일 소스로 유지
  2. LaTeX(라텍스) 수식이 깨지지 않고 Manim(매니메이션 엔진)에서 안정 렌더링
  3. “판서 느낌” 연출이 템플릿화되어 반복 제작 속도 최대화
  4. 5분 롱폼에서 장면(Scene) 분할, 타이밍, 내레이션 싱크를 반자동화
  5. 실패 지점(수식 컴파일, 폰트, 줄바꿈, 길이)을 자동 검증

아래는 내가 추천하는 Manim 기반 반자동 파이프라인 설계안이다. 그대로 구축하면 “Markdown만 고치면 영상이 다시 뽑히는” 흐름까지 간다.

전체 구조

입력

  • notes.md: 본문(설명 텍스트 + 수식)
  • meta.yml: 장면별 옵션(템포, 카메라, 판서 스타일, 내레이션 구간)
  • assets/: 이미지, 참고 도표

변환

  • build.py: Markdown 파서 + AST(abstract syntax tree, 추상 구문 트리) 생성
  • emit.py: AST → Manim Scene 코드 생성
  • render.sh: 렌더(저해상도 프리뷰, 고해상도 최종)

출력

  • out/preview.mp4
  • out/final.mp4
  • out/script.txt (자막/내레이션 스크립트 자동 추출)

Markdown 문법을 “영상 제작용”으로 제한하기

일반 Markdown을 다 지원하려고 하면 오히려 느려진다. 아래 정도로 제한하면 안정성과 자동화가 크게 오른다.

  1. 장면 구분
  • H2(heading level 2, 2단계 제목) ## 를 Scene 경계로 사용
  1. 블록 타입
  • 일반 문단: 설명 텍스트
  • 수식 블록: $$ ... $$
  • “단계 전개”: 번호 리스트 1. 2. 3. 를 “한 줄씩 등장”으로 매핑
  • 강조 박스: 커스텀 fenced block

예시 notes.md

markdown
## Def: Convexity
Convex function is defined as:

$$
f(\lambda x + (1-\lambda)y) \le \lambda f(x) + (1-\lambda)f(y)
$$

Steps:
1. Fix $x,y$
2. Choose $\lambda\in[0,1]$
3. Compare chord and graph

```chalk
Write the inequality slowly.
Pause after the LHS appears.

AST 설계

최소 노드만 둔다.

  • Scene(title, blocks[])
  • Paragraph(text)
  • DisplayMath(latex)
  • Steps(items[])
  • ChalkNote(text) # 렌더에는 안 나오고 타이밍/연출 힌트로만 사용

이 AST를 만들면, 이후는 “노드별 렌더러”만 바꾸면 된다.

렌더링 템플릿

장기 생산 속도를 좌우하는 부분이다. 핵심은 “연출 스타일을 코드로 고정”하는 것.

스타일 목표

  • 텍스트는 너무 타이핑스럽지 않게, 약간의 손글씨 느낌(등장 애니메이션이 stroke 기반)
  • 수식은 Write 계열로 “써지는 느낌”
  • 단계는 한 줄씩 등장 + 이전 줄은 약간 옅게(가독성 유지)

Manim 템플릿(핵심만)

python
from manim import *

class ChalkStyle:
    def __init__(self):
        self.text_font = "STIX Two Text"
        self.math_font = "STIX Two Math"
        self.line_spacing = 0.9
        self.scale = 0.9
        self.write_time = 1.0
        self.step_time = 0.6

def mk_paragraph(s: str, style: ChalkStyle):
    return Paragraph(
        *s.split("\n"),
        font=style.text_font,
        line_spacing=style.line_spacing
    ).scale(style.scale)

def mk_math(latex: str, style: ChalkStyle):
    # latex는 $$ $$ 제거 후 들어온다고 가정
    return MathTex(latex, font_size=42)

def anim_write(mobj, style: ChalkStyle):
    return Write(mobj, run_time=style.write_time)

판서 느낌을 더 올리고 싶으면 “stroke reveal”을 강화하는 방식으로 간다. 다만 LaTeX의 glyph를 진짜 필기 경로로 바꾸는 건 비용이 커서, 장기 생산 목적이면 Write/Create 조합 + 약간의 jitter(위치/시간 미세 흔들림) 정도가 현실적인 최적점이다.

Markdown → AST 파서

Python(파이썬)에서 markdown-it-py 또는 mistune 같은 파서로 토큰화한 뒤, 위에서 정한 “지원 문법만” AST로 만든다.

간단 파서 스케치(핵심 로직만)

python
import re
from dataclasses import dataclass

@dataclass
class Scene: title: str; blocks: list
@dataclass
class Paragraph: text: str
@dataclass
class DisplayMath: latex: str
@dataclass
class Steps: items: list
@dataclass
class ChalkNote: text: str

def parse(md: str) -> list[Scene]:
    scenes = []
    cur = None
    buf = []

    def flush_paragraph():
        nonlocal buf, cur
        if not buf: return
        text = "\n".join(buf).strip()
        if text:
            cur.blocks.append(Paragraph(text))
        buf = []

    lines = md.splitlines()
    i = 0
    while i < len(lines):
        line = lines[i]

        m = re.match(r"^##\s+(.*)$", line)
        if m:
            if cur:
                flush_paragraph()
                scenes.append(cur)
            cur = Scene(title=m.group(1).strip(), blocks=[])
            i += 1
            continue

        if line.strip().startswith("```chalk"):
            flush_paragraph()
            i += 1
            note_lines = []
            while i < len(lines) and not lines[i].strip().startswith("```"):
                note_lines.append(lines[i])
                i += 1
            cur.blocks.append(ChalkNote("\n".join(note_lines).strip()))
            i += 1
            continue

        if line.strip() == "$$":
            flush_paragraph()
            i += 1
            math_lines = []
            while i < len(lines) and lines[i].strip() != "$$":
                math_lines.append(lines[i])
                i += 1
            cur.blocks.append(DisplayMath("\n".join(math_lines).strip()))
            i += 1
            continue

        if re.match(r"^\d+\.\s+", line):
            flush_paragraph()
            items = []
            while i < len(lines) and re.match(r"^\d+\.\s+", lines[i]):
                items.append(re.sub(r"^\d+\.\s+", "", lines[i]).strip())
                i += 1
            cur.blocks.append(Steps(items))
            continue

        buf.append(line)
        i += 1

    if cur:
        flush_paragraph()
        scenes.append(cur)
    return scenes

AST → Manim Scene 코드 생성

방법 A: 한 파일에 “자동 생성 Scene 클래스”들을 생성

방법 B: 하나의 GenericScene이 AST를 읽어서 런타임에 렌더

장기 생산에서는 B가 좋다. 즉 “코드를 생성하지 않고 데이터만 바꿔서 렌더”.

예: JSON(제이슨)으로 AST 덤프 후, Manim에서 읽기.

build.py

  • notes.md → scenes.json

manim_project/video.py

  • scenes.json 읽어서 순서대로 렌더

GenericScene 스케치

python
import json
from manim import *

class Video(Scene):
    def construct(self):
        data = json.load(open("out/scenes.json", "r", encoding="utf-8"))
        style = ChalkStyle()

        for sc in data:
            self.render_scene(sc, style)
            self.clear()

    def render_scene(self, sc, style):
        title = Text(sc["title"], font=style.text_font).scale(0.8).to_edge(UP)
        self.play(FadeIn(title, shift=DOWN), run_time=0.4)

        y = 2.6
        for blk in sc["blocks"]:
            t = blk["type"]
            if t == "Paragraph":
                m = mk_paragraph(blk["text"], style).to_corner(UL).shift(DOWN*0.6)
                self.play(FadeIn(m, shift=UP*0.1), run_time=0.35)
            elif t == "DisplayMath":
                m = mk_math(blk["latex"], style).next_to(title, DOWN, buff=0.6)
                self.play(anim_write(m, style))
            elif t == "Steps":
                self.render_steps(blk["items"], title, style)
            elif t == "ChalkNote":
                # 렌더 없음: 타이밍 힌트로만 사용
                pass

    def render_steps(self, items, anchor, style):
        group = VGroup()
        for k, it in enumerate(items):
            line = Tex(rf"{k+1}.\;\; {it}")
            group.add(line)
        group.arrange(DOWN, aligned_edge=LEFT, buff=0.25).next_to(anchor, DOWN, buff=0.8).to_edge(LEFT)

        for k, line in enumerate(group):
            self.play(Write(line), run_time=style.step_time)
            if k >= 1:
                group[k-1].set_opacity(0.55)

타이밍과 내레이션 싱크 반자동화

meta.yml을 둬서 Scene별로 템포를 바꾼다.

meta.yml 예시

yaml
default:
  write_time: 1.0
  step_time: 0.6
scenes:
  - title: "Def: Convexity"
    write_time: 1.2
    pause_after_math: 0.5

ChalkNote에 “Pause after …” 같은 힌트를 넣어두고, 파서가 이를 meta 이벤트로 변환해 self.wait()를 자동 삽입하면 제작 속도가 올라간다.

자동 검증(실패를 빨리 발견)

  1. LaTeX 컴파일 검증
  • scenes.json 생성 단계에서 MathTex용 latex를 한 번 “dry run” 검사(가능하면)
  • 최소로는 $ 짝, \begin{} 짝, 금지 문자 체크
  1. 길이 검증
  • 문단이 너무 길면 자동으로 Scene을 쪼개거나 경고 출력
  1. 렌더 프리뷰
  • 저해상도(예: 480p, low quality)로 빠르게 전체를 뽑고, 최종만 high quality

render.sh 예시

bash
python build.py notes.md meta.yml
manim -pql manim_project/video.py Video
# 최종
manim -pqh manim_project/video.py Video

운영 팁: 생산 속도를 올리는 설계 선택

  • 스타일을 바꾸고 싶을 때, Markdown을 고치지 말고 ChalkStyle만 고치게 한다.
  • “한 Scene = 하나의 개념 단위”로 자른다. 5분이면 6~12개 Scene이 적당하다.
  • Steps를 적극 사용한다. 수식이 많아도 Steps가 있으면 판서 전개가 자동으로 자연스러워진다.
  • 이미 있는 Markdown을 “영상용 Markdown”으로 최소 수정해서 유지한다.

자체 검증

설정한 기준 1~5에 대해 점검

  • Markdown 단일 소스: 가능(notes.md 중심)
  • LaTeX 안정성: MathTex 기반으로 만족
  • 판서 느낌: Write/Create 중심으로 템플릿화 가능
  • 장기 생산 속도: GenericScene + JSON AST로 매우 높음
  • 자동 검증: 빌드 단계 경고/프리뷰 렌더로 만족

원하면, 다음을 바로 제공할 수 있다.

  • 위 구조를 실제 폴더로 구성한 최소 동작 예제(파일 5~7개)
  • “수식 변형 중심” 템플릿(TransformMatchingTex 기반)과 “판서 전개 중심” 템플릿 두 종류