본문으로 건너뛰기
5분 AI.
TRENDSHIFT MONTHLY #17 딥다이브 · 2026-10-23 분석

filmcraft 딥다이브
— 한 번의 자르기 뒤에 숨은 영상 편집 시스템

영상 편집기

빠른 판단

무엇
영상·음성·자막의 타임라인 편집·합성을 GUI·CLI·MCP에서 수행하는 Rust 비선형 편집기
이럴 때
여러 미디어를 컷·트랙·자막으로 편집하고 공통 엔진에 명령을 보내 렌더링 작업을 자동화할 때
입력
영상·오디오·자막 클립과 타임라인 편집 명령
결과
편집 프로젝트와 합성·인코딩한 영상 파일
영상 클립을 자르는 작은 동작에는 시간 계산, 문서 변경, 실행 취소, 미리보기와 파일 해석이 함께 들어 있습니다. FilmCraft는 그 연결을 Rust 소스에서 따라가 볼 수 있는 비선형 영상 편집기입니다. 창이 없는 엔진을 중심으로 egui 화면·CLI·MCP·WebAssembly 앱이 만나는 구조를 초보자의 눈높이에서 읽습니다. 저장소: storytold/filmcraft · ⭐15,809 · 포크 6,630 · 최신 릴리스 v0.12.0 · 2026-10-23 GitHub 기준. 소스 설명과 파일 경로는 커밋 abf44fd 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

영상 편집기를 읽으며, 상태와 시간을 다루는 법을 배운다

FilmCraft는 여러 영상·음성·자막을 타임라인에 배치하고, 편집·합성한 결과를 영상 파일로 내보내는 Rust 기반 비선형 편집기입니다. 핵심 로직은 crates/engine에 있고, 화면은 crates/ui-egui라는 별도 부품입니다. 같은 편집 명령을 사람이 화면에서 누르거나 자동화 도구가 호출해도, 공통 엔진이 처리하도록 설계했습니다.

용어
비선형 편집기와 타임라인
비선형 편집은 원본을 순서대로 덮어쓰는 대신, 원하는 장면을 원하는 위치에 배치하는 방식입니다. 타임라인은 그 배치표입니다. 원본 파일과 그 파일을 어느 구간에서 보여 줄지 적은 문서를 구별하면 이해가 쉬워집니다.
한 문장 비유

타임라인은 요리 순서표이고, 엔진은 같은 주문을 받아 처리하는 주방입니다

영상 원본이 재료라면 클립은 “이 재료의 여기부터 3초만 쓰자”라는 지시입니다. 미리보기는 그 시점의 한 접시를 만들고, 내보내기는 모든 시점의 접시를 차례로 만들어 포장합니다.

화면·CLI·MCP는 주문을 받는 서로 다른 창구입니다. 창구마다 자르기 규칙을 다시 만들지 않고 같은 주방에 주문을 전달하므로, 편집 규칙과 사용자 인터페이스를 따로 공부할 수 있습니다.

1-1. 영상 파일 하나가 결과물이 되기까지

  1. 가져오기: 컨테이너에서 영상·음성 스트림과 시간 정보를 읽습니다.
  2. 문서 구성: Project 안에 미디어와 Sequence를 등록합니다.
  3. 편집: TrackItem의 시작 시각·원본 시작점·길이를 바꿉니다.
  4. 재생: 현재 시각에 보일 프레임을 디코딩하고 효과와 합성을 적용합니다.
  5. 내보내기: 렌더링한 프레임을 인코딩한 뒤 컨테이너에 기록합니다.

crates/project/src/lib.rs의 MediaRef::File은 경로를 저장하고, crates/edit/src/lib.rs의 split_track_at()은 클립 문서를 나눕니다. 이 함수를 실행한다고 원본 동영상의 바이트를 두 파일로 잘라 저장하는 것은 아닙니다.

학습 목표
“영상 편집 기능 목록”보다 “기능들이 연결되는 방식”에 주목하기

Rust 문법을 모두 이해할 필요는 없습니다. 처음에는 명령 → 문서 변경 → 새 스냅샷 → 렌더링의 흐름만 잡으세요. 이 연결을 이해하면 그림판, 문서 편집기, CAD처럼 변경 이력이 중요한 다른 앱에도 같은 사고방식을 적용할 수 있습니다.

2왜 주목받는가

관심을 끄는 설계와, 실제 제품의 완성도를 나누어 읽기

TrendShift Monthly #17 선정작이며, 2026-10-23 GitHub 기준 스타 15,809개와 포크 6,630개를 기록했습니다. 생성일은 2026-09-30입니다. 짧은 공개 기간에 관심이 모인 점은 확인할 수 있지만, 스타 증가의 원인이나 실제 사용자 수를 이 숫자만으로 알 수는 없습니다. 다음은 소스와 README에서 읽히는 관심 요인에 대한 해석입니다.

2-1. 화면 아래의 코덱까지 읽을 수 있다

README는 주요 코덱을 공개 명세에서 직접 구현했다고 설명합니다. 실제로 crates/h264, crates/hevc, crates/av1, crates/aac와 MP4/MOV를 담당하는 crates/isobmff가 분리되어 있습니다. 입문자가 곧바로 H.264를 구현하기는 어렵지만, “파일을 읽는다”는 한 줄 아래에 어떤 계층이 있는지 보기에는 좋은 표본입니다.

2-2. 자동화를 제품의 입구로 둔다

crates/engine/src/commands.rs는 명령 식별자, 표시 이름, 매개변수 설명, 실행 가능 조건과 실행 함수를 함께 등록합니다. crates/automation/src/server.rs의 FilmcraftMcp::run()은 창 없는 세션에서는 Session::execute()를 호출하고, 연결 모드에서는 실행 중인 앱으로 전달합니다. “AI 기능”이라는 이름보다 외부 프로그램이 안정적인 동작 단위에 접근한다는 점이 학습상 중요합니다.

2-3. 경쟁 제품과 비교할 때 볼 축

프로젝트가 비교 목표로 삼는 제품은 Adobe Premiere Pro입니다. 아래 표는 docs/target-app-parity.md와 docs/gaps.md가 제시한 목표를 바탕으로, FilmCraft의 장점과 아직 남은 차이를 정리한 것입니다. 독립적인 기능 점수나 속도 순위는 아닙니다.

비교 축FilmCraft의 장점경쟁 목표와의 차이
소스 학습편집 규칙부터 GPU 셰이더까지 Rust 중심으로 연결해서 읽을 수 있음제품 수준의 호환성과 안정성은 코드 공개 여부만으로 보장되지 않음
자동화 진입점GUI·CLI·MCP가 공유하는 명령 레지스트리Premiere의 자동화 체계와 같은 API라는 뜻은 아니며 기존 도구의 변환이 필요함
화면과 엔진엔진 아래에 egui 의존성을 들이지 않는 계층 규칙익숙한 화면을 목표로 해도 세부 조작감과 효과 결과는 별도 검증 대상
프로젝트 교환FCP7 XML·FCPXML·OTIO·AAF·OMF 등의 구현을 공부할 수 있음README 기준 Premiere의 .prproj 직접 열기는 미지원
확장 생태계내장 효과와 코덱의 구현을 직접 수정할 수 있는 구조README 기준 VST3·Audio Units·OpenFX 호스팅은 미지원
제품 성숙도미완성 영역을 문서로 공개하며 설계 의도를 추적하기 쉬움현재 단계는 alpha이며 상용 제작 환경을 대체한다고 단정하기 어려움

이 저장소의 뚜렷한 학습상 장점은, 편집 화면의 동작을 파일 해석과 프레임 계산까지 거슬러 올라가 볼 수 있다는 점입니다.

수치의 의미
기능이 “있다”와 제작에 “충분하다”는 다릅니다

README의 기능 범위 약 86%, 실제 작업 준비도 약 56%는 2026-10-10 재평가를 인용한 프로젝트 자체 지표입니다. 벤치마크 점수나 외부 인증으로 읽으면 안 됩니다. docs/gaps.md도 효과 이름의 존재와 결과의 정확성을 구별합니다.

3기술 스택 전체 지도

서버·웹 프레임워크의 조합 대신, Rust 작업 공간의 계층을 읽는다

이 앱에서 “백엔드”는 원격 HTTP 서버보다 창 없이 동작하는 로컬 엔진을 뜻합니다. 프론트엔드는 egui이고, 데스크톱 호스트가 운영체제의 창·오디오·파일 대화상자를 연결합니다. 일반 편집에 데이터베이스나 웹 서버 배포를 전제로 한 구조는 아닙니다.

3-1. 백엔드: 문서·편집·미디어 엔진

부품선언 / 잠금 버전역할과 근거 파일
Rustedition = "2024"
rust-version = "1.95"
루트 Cargo.toml의 공통 패키지 설정. 문서도 stable ≥ 1.95를 요구
serde"1" / 1.0.229루트 Cargo.toml. 문서·상태를 직렬화하며 derive, rc 기능 사용
serde_json"1" / 1.0.151명령 매개변수, 프로젝트 형식, 제어 메시지의 JSON 표현
thiserror"2" / 2.0.21공통 오류 정의. 잠금 파일에는 전이 의존성용 1.0.69도 존재
rayon"1" / 1.12.0CPU 병렬 처리. crates/export/src/job.rs의 프레임 묶음 렌더링 등에 사용
image"0.25" / 0.25.10crates/media/Cargo.toml. PNG·JPEG·GIF·WebP·TIFF·BMP 기능을 선택
symphonia"0.5.5" / 0.5.5crates/codecs/Cargo.toml. 일부 오디오 해석에 쓰는 외부 Rust 라이브러리
harfrust"0.12" / 0.12.0crates/text/Cargo.toml. OpenType 문자 모양 배치를 담당

표의 왼쪽 버전은 각 Cargo.toml의 허용 범위이고, 오른쪽은 Cargo.lock에 기록된 선택 버전입니다. 예를 들어 "0.25"와 0.25.10은 서로 모순되지 않습니다. 선언과 실제 해석 결과를 구별하는 연습이 됩니다.

용어
Cargo workspace와 crate
crate는 Rust가 컴파일하는 라이브러리나 실행 프로그램의 단위입니다. workspace는 여러 패키지를 묶어 버전·의존성·검사를 관리하는 틀입니다. FilmCraft는 공통 버전을 0.12.0으로 선언하고, 하위 패키지가 version.workspace = true로 이어받습니다.

3-2. 프론트엔드: Rust UI와 GPU

부품선언 / 잠금 버전실제 연결
egui / eframe"0.36" / 각각 0.36.2crates/ui-egui의 패널과 apps/filmcraft의 앱 호스트. eframe에 wgpu·accesskit 기능 사용
wgpu"30" / 30.0.1crates/gpu/Cargo.toml. WGSL 셰이더로 합성·효과를 계산
cpal"0.16" / 0.16.0apps/filmcraft/Cargo.toml. 네이티브 오디오 출력
rfd"0.17" / 0.17.2데스크톱의 파일 선택 대화상자
craft-ui0.1.0 + Git 리비전루트 매니페스트에서 5c874d51…에 고정한 공유 UI 부품
wasm-bindgen"=0.2.129" / 0.2.129apps/filmcraft-web/Cargo.toml. 브라우저와 Rust 사이 연결. CLI 도구도 같은 버전 필요

React나 Tauri가 중심인 앱이 아닙니다. 핵심 UI 의존성은 egui/eframe입니다. 웹 앱도 별도의 TypeScript 화면을 다시 만드는 대신 같은 엔진과 UI를 wasm32-unknown-unknown으로 컴파일합니다. 다만 브라우저의 파일·오디오·디코더에는 web-sys와 JavaScript 연결 코드가 필요합니다.

3-3. 자동화·인프라·빌드

영역설정공부할 내용
MCPrmcp = "3.5" → 3.5.0
tokio = "1" → 1.53.1
crates/automation/Cargo.toml에서 서버·매크로·입출력 전송 기능 선택
개발 최적화외부 패키지 opt-level = 2 / 기본 개발 프로필 1루트 Cargo.toml. 컴파일 속도와 실행 속도의 절충
검사 실행기xtask/src/main.rs::ci()형식, release 모드 lint·테스트, 계층, 자산, WASM 검사 순서
PR CIubuntu-24.04, 제한 120분.github/workflows/ci.yml. main push·PR·수동 실행에서 품질 검사
CI 컴파일LTO 끄기 / 코드 생성 단위 16배포용 최적화 설정을 테스트 바이너리의 링크 비용에 맞게 덮어씀
릴리스craft-common-actions 공유 워크플로.github/workflows/release.yml이 서명·배포 작업을 외부 공통 설정에 위임

빌드의 중심 파일은 Cargo.toml과 Cargo.lock입니다. 추적 파일 목록에는 package.json, pyproject.toml, go.mod, Dockerfile이 없습니다. 웹 대상이 있다고 해서 Node.js 설치나 컨테이너 서버가 필수인 것은 아닙니다.

의존성 목록을 읽을 때는 이름만 외우기보다, 어느 계층이 그 라이브러리를 가져오는지 함께 확인하세요. 특히 cpal은 데스크톱 호스트에, rmcp는 자동화에 두어 편집 규칙과 분리합니다.

문서와 설정의 차이
“순수 Rust”를 “외부 부품이 전혀 없음”으로 읽지 않기

자체 코덱이 많지만 symphonia와 image 등의 라이브러리도 사용합니다. 운영체제 미디어 API를 호출하는 crates/platform은 제한된 FFI 영역에서 unsafe를 허용합니다. 제품에 FFmpeg를 포함하지 않는다는 README 설명과, 테스트에서 FFmpeg를 외부 비교 도구로 쓴다는 설명은 함께 성립합니다.

4아키텍처 심화 분석

한 번의 자르기를 따라가며 네 가지 설계 원리를 연결한다

4-1. 전체 시스템 구조도

아래는 주요 호출과 데이터 흐름을 단순화한 그림입니다. 위쪽은 사용자의 요청을 받는 부분, 가운데는 편집 상태를 바꾸는 부분, 아래쪽은 영상과 음성을 실제로 계산하는 부분입니다. 모든 의존성 선을 나열한 그래프는 아닙니다.

[데스크톱 사용자] [CLI / 외부 에이전트] [브라우저 사용자] | | | apps/filmcraft apps/filmcraft-cli apps/filmcraft-web | | | ui-egui CLI 직접 실행 / automation(MCP) ui-egui | | | menus::invoke 로컬 세션 호출 menus::invoke | | | +---------------------------+--------------------------+ | Session::execute(id, params) crates/engine (L4) Arc<Project> 스냅샷 · History 이력 | +-------------------+---------------------+ | | | 문서 편집 미리보기 내보내기 crates/edit crates/render crates/export | / \ | crates/project CPU 렌더링 plan_frame 프레임 요청 편집 문서 모델 | / \ FramePlan CPU 렌더링 FrameRenderer | | crates/gpu <--------------+ WGSL / wgpu 합성 내보내기: 렌더링한 프레임 --> 인코딩 / 컨테이너 --> 결과 파일 프레임 입력: 소스 공급자 --> crates/media · crates/codecs · crates/frame 기초 연산: crates/time · crates/color · crates/bitstream CLI·MCP 연결 모드: BridgeClient --> 데스크톱 제어 채널 --> 위 Session crates/platform: 운영체제 하드웨어 디코더·인코더를 연결하는 경계

계층 표는 xtask/src/main.rs의 LAYERS에 있습니다. layers()는 상위 계층으로 향하는 내부 의존성을 거부하고, L5 아래에서 egui·eframe·cpal 같은 UI·OS 부품을 참조하지 못하게 검사합니다. L1 이상에서 같은 계층 간 연결도 SAME_LAYER에 허용된 경우만 통과합니다.

4-2. 패턴 ① 명령 레지스트리: 버튼과 API가 같은 규칙을 만난다

crates/engine/src/commands.rs::CommandSpec에는 id, label, menu, shortcut, params, enabled, run, journal이 있습니다. 표시할 정보와 실제 동작이 한 항목에 모여 있으므로 메뉴, 명령 검색, 자동화가 같은 목록을 참고할 수 있습니다.

// CommandSpec의 일부 필드: 원본 선언에서 발췌
pub id: &'static str,
pub params: &'static str,
pub enabled: Enabled,
pub run: Run,
pub journal: bool,

// 실제 타입 별칭
type Run = fn(&mut Session, &Value) -> Result<Value>;
type Enabled = fn(&Session) -> std::result::Result<(), String>;

params 자체는 설명 문자열이고, 호출 시 매개변수는 serde_json::Value입니다. 따라서 “등록만 하면 모든 명령의 입력 타입이 컴파일 시점에 검증된다”고 이해하면 안 됩니다. 각 핸들러의 파싱과 유효성 검사가 실제 경계입니다.

  1. crates/ui-egui/src/menus.rs::invoke()가 엔진 명령을 Session::execute()로 전달합니다.
  2. execute()가 commands::find()로 명령을 찾고 enabled_for()로 실행 조건을 확인합니다.
  3. spec.run이 편집 함수를 호출합니다. 성공한 변경 명령은 저널에도 남습니다.
  4. MCP의 FilmcraftMcp::run()도 같은 엔진 입구를 사용합니다. 창 없는 모드에서는 Mutex로 세션 접근을 조정합니다.

예를 들어 timeline.razor는 트랙이나 클립을 고른 뒤 Session::edit_sequence() 안에서 edit::razor() 또는 edit::razor_items()를 호출합니다. 클립을 지정하고 연결 선택이 켜져 있으면 with_links()가 연결된 영상·음성 클립까지 처리 대상을 확장합니다.

// CLI 일괄 명령의 한 줄: 활성 시퀀스의 V1, 3초 지점
{"id": "timeline.razor", "params": {"track": "V1", "time": 762048000000}}

이 JSON 한 줄은 CLI의 run 하위 명령이 읽는 JSONL 항목입니다. TCP 제어 채널에서는 engine.execute라는 메서드와 인증 토큰을 담은 별도 요청 형식이 필요합니다. 전송 방식이 달라도 명령 식별자와 편집 규칙은 공유합니다.

4-3. 패턴 ② 스냅샷과 트랜잭션: 실패한 편집은 현재 문서를 바꾸지 않는다

crates/engine/src/lib.rs::Session::edit()는 먼저 프로젝트와 편집 상태를 복제하고, 복제본에 변경을 적용한 뒤 성공했을 때만 현재 프로젝트를 교체합니다. 시퀀스 중첩에 새 순환 참조가 생기지 않는지도 refuse_self_nesting()으로 확인합니다.

// Session::edit()에서 핵심 순서를 발췌
let mut p = (*self.project).clone();
let mut st = self.state.clone();
let r = f(&mut p, &mut st)?;
self.refuse_self_nesting(&p)?;
let old = std::mem::replace(&mut self.project, Arc::new(p));
self.history.undo.push((label.to_string(), old));

?는 오류가 나면 즉시 반환합니다. 위 코드에서 오류가 생기면 교체 단계에 도달하지 않으므로, 이 편집 경로의 현재 프로젝트와 상태가 유지됩니다. edit_sequence_as()는 여기에 Sequence::check() 검사도 더합니다. 여러 편집을 묶는 edit_group()에는 실패 시 이력과 문서를 되돌리는 처리도 있습니다.

비유

원본 설계도에 바로 낙서하지 않고 복사본에서 수정한 뒤, 검수가 끝난 복사본만 새 정본으로 채택하는 방식입니다. 이전 정본을 버리지 않으면 실행 취소가 됩니다. 렌더러가 이전 정본을 읽는 동안에도 새 정본을 준비할 수 있습니다.

History의 두 스택은 Vec<(String, Arc<Project>)>입니다. undo()는 이전 프로젝트를 꺼내 현재 것과 교환하고, 현재 것을 redo 스택에 넣습니다. edit_merged()는 다른 편집 없이 같은 키·라벨로 이어지는 슬라이더 드래그를 한 번의 실행 취소 단계로 합칩니다.

비용도 구별해야 합니다. Arc::clone()으로 기존 스냅샷을 공유하는 일은 가볍지만, 위 Project::clone()은 프로젝트 데이터를 복제하는 경로입니다. 모든 편집이 상수 시간이라는 뜻도, 모든 하위 자료구조가 영속 자료구조라는 뜻도 아닙니다. 또 Session::new()는 History { limit: 200, ... }으로 초기화합니다. README의 “무제한 undo”보다 실제 기본 설정이 구체적입니다.

4-4. 패턴 ③ 정확한 시간 모델: 초 대신 정수 Tick으로 편집한다

crates/time/src/lib.rs는 Tick(pub i64)와 초당 254_016_000_000 Tick을 정의합니다. 프레임률은 실수 하나가 아니라 FrameRate { num, den }으로 저장합니다. 흔히 29.97이라고 부르는 규격은 여기서 정확히 30000 / 1001입니다.

단위Tick 계산의미
1초254,016,000,000시간 계산의 공통 눈금
24 fps의 한 프레임10,584,000,000초당 눈금을 24로 나눈 정수
30000/1001 fps의 한 프레임8,475,667,200초당 눈금 × 1001 ÷ 30000
48 kHz의 한 샘플5,292,000영상과 음성을 같은 눈금 위에 배치
// 학습용 계산 예시: crates/time의 공개 API
use filmcraft_time::{FrameRate, Tick};

let rate = FrameRate::FPS_29_97;
let first_frame = rate.frame_duration();
let sample = Tick::from_units(1, 48_000);
// first_frame.0 = 8_475_667_200
// sample.0 = 5_292_000

FrameRate::frame_duration()은 중간 계산에 i128을 사용하고, sane()은 분자나 분모가 0 이하인 프레임률을 기본값으로 바꿉니다. TimeRange는 [start, end)인 반열린 구간이므로, 맞닿은 두 클립의 경계 시각이 양쪽에 동시에 포함되지 않습니다.

정확성의 범위도 있습니다. 흔한 방송 프레임률은 정수로 표현되지만, 임의의 특이한 비율은 나눗셈에서 버림이 생길 수 있습니다. Tick::from_seconds_f64()는 경계 입력을 반올림하고, edit::split_track_at()의 배속 계산에는 f64가 사용됩니다. 따라서 “코드 전체에서 실수를 전혀 쓰지 않는다”보다 편집 시각의 기준을 정수로 통일한다가 정확합니다.

4-5. 패턴 ④ CPU 기준 구현과 GPU 가속 경로

crates/render/src/lib.rs::render_sequence()는 시각·크기·소스를 받아 선형광 이미지로 렌더링합니다. crates/render/src/plan.rs::plan_frame()은 GPU가 처리할 수 있는 레이어를 계획으로 표현합니다. FramePlan은 Layers, Composite, Image 세 경우를 구별합니다.

crates/gpu/src/export_renderer.rs::ExportRenderer::render()에도 GPU 내보내기 경로가 있습니다. 다만 FramePlan::Composite는 None을 반환해 CPU 처리로 넘깁니다. README의 “내보내기 렌더링은 CPU”라는 상태 설명만으로는 이 구현을 다 설명하지 못합니다.

crates/gpu/src/tests.rs::yuv_code_range_gpu_matches_cpu()는 8·10·12·16비트 입력을 CPU와 GPU로 처리해 비교합니다. 해당 테스트는 8비트 RGBA 출력에서 채널별 최대 오차를 1로 허용합니다. 그러므로 “같은 결과를 목표로 한다”와 “모든 경로의 모든 바이트가 항상 같다”도 구분해야 합니다.

빠른 구현을 만들 때는, 먼저 비교할 수 있는 기준 구현을 두고 지원하지 않는 경우 돌아갈 길까지 설계해야 합니다.

용어
선형광과 미리 곱한 알파
선형광은 빛의 양에 맞춰 색을 계산하는 공간입니다. 화면에 보이는 색 숫자를 무작정 평균 내는 것과 결과가 다를 수 있습니다. 미리 곱한 알파는 RGB에 투명도를 먼저 반영하는 표현으로, 반투명 경계를 합성할 때 계산을 일관되게 만들기 위한 선택입니다.

5디렉토리 구조 해부

40만 줄을 처음부터 읽지 않고, 질문마다 읽을 입구를 고른다

추적 중인 파일은 1,393개이며, crates/ 아래 패키지 50개, apps/ 아래 패키지 3개, 그리고 xtask가 있습니다. 아래 트리는 핵심 부분만 발췌한 것으로, 모든 폴더의 목록은 아닙니다.

filmcraft/ |-- Cargo.toml / Cargo.lock 공통 버전·의존성과 잠금 결과 |-- apps/ | |-- filmcraft/ 데스크톱 창, 오디오, 제어 서버 | |-- filmcraft-cli/ 창 없는 명령 도구와 MCP 시작점 | `-- filmcraft-web/ 브라우저 파일·오디오·복구 연결 |-- crates/ | |-- time/ color/ geom/ 시간·색·기하의 기본 규칙 | |-- bitstream/ 비트 단위 입출력 | |-- isobmff/ matroska/ avi/ 컨테이너 파서 | |-- h264/ hevc/ av1/ aac/ 주요 코덱 구현 | |-- frame/ media/ codecs/ 프레임 표현·소스·디코더 연결 | |-- project/ edit/ 편집 문서와 편집 연산 | |-- render/ gpu/ export/ 합성·GPU·파일 출력 | |-- text/ captions/ 글자 배치와 자막 | |-- audio-dsp/ 오디오 효과와 계측 | |-- speech/ tts/ tts-text/ 선택적 음성 기능 | |-- format/ engine/ 저장·복구·세션·명령 | |-- ui-egui/ automation/ 화면과 외부 자동화 | `-- platform/ testkit/ golden/ OS 경계와 검증 보조 |-- xtask/ 계층·자산·WASM·CI 검사 |-- docs/ 구조·한계·프로토콜 문서 |-- packaging/ 플랫폼별 배포 구성 |-- assets/ 라이선스가 붙은 자산 `-- .github/workflows/ CI와 릴리스 진입점

5-1. 질문으로 고르는 읽기 순서

궁금한 것먼저 열 파일다음에 확인할 것
색 문자열은 어떻게 검증할까?crates/color/src/lib.rsparse_hex()와 바로 아래 회귀 테스트
자르기는 무엇을 바꿀까?crates/edit/src/lib.rssplit_track_at(), razor(), 길이와 원본 시작점
실행 취소는 어디서 만들까?crates/engine/src/lib.rsedit(), edit_merged(), undo()
한 프레임은 어떻게 나오나?crates/render/src/lib.rsrender_sequence()를 읽고 crates/render/src/plan.rs의 GPU 계획 경로와 비교
하드웨어가 다르면?crates/codecs/src/lib.rs디코더 등록 순서와 software_video_decoder()
앱 없이 조작할 수 있나?crates/automation/src/server.rsBackend, FilmcraftMcp::run()

5-2. 코드 규모는 이렇게 읽는다

언어 / 영역파일 수물리적 줄 수
Rust 전체1,015407,079
WGSL51,422
Python111,560
셸 / PowerShell10 / 2808 / 231
HTML / JavaScript1 / 195 / 59

커밋 abf44fd의 Git 추적 파일을 확장자별로 센 값입니다. 빈 줄·주석·테스트·소스에 들어 있는 표 데이터도 포함하며, 의존성 다운로드와 빌드 산출물은 포함하지 않습니다. 따라서 실행 로직만 센 순수 코드 줄 수와는 다릅니다.

Rust 가운데 crates/ui-egui는 75,495줄, crates/engine은 57,323줄, crates/render는 23,281줄입니다. 시간 계산만 떼면 crates/time은 922줄입니다. 큰 제품 안에서도 학습 단위를 작게 고를 수 있습니다.

읽기 팁
구현 파일과 테스트 파일을 한 쌍으로 읽기

crates/edit/src/tests.rs, crates/engine/src/trim_tests.rs, crates/gpu/src/tests.rs처럼 이름이 대응하는 테스트에서 먼저 입력과 기대 결과를 찾으세요. 함수 전체를 이해하기 전에도 “이 연산이 지켜야 하는 약속”을 알 수 있습니다.

6학습 포인트 (기술별)

라이브러리 이름을 외우는 대신, 이 코드가 푸는 문제를 배운다

6-1. Rust: 실패를 반환값으로 표현하기

커밋 abf44fd의 색상 입력 검증은 입문하기 좋은 예제입니다. crates/color/src/lib.rs::parse_hex()는 앞뒤 공백을 정리하고 선택적인 # 하나를 뺀 뒤, 모든 바이트가 ASCII 16진수 문자인지 검사합니다. 길이가 6이면 RGB, 8이면 RGBA로 처리하고 나머지는 None을 반환합니다.

// parse_hex()의 입력 검증 부분
let s = s.trim();
let s = s.strip_prefix('#').unwrap_or(s);
if !s.bytes().all(|b| b.is_ascii_hexdigit()) {
    return None;
}

unwrap_or(s)는 값이 없을 때 원래 문자열을 쓰는 대체 처리입니다. 값이 없으면 중단되는 unwrap()과 다릅니다. 이어지는 s.get(i..i + 2)?는 잘못된 문자열 구간도 실패로 표현합니다. 문자열 길이뿐 아니라 허용된 문자도 검사해야 유효한 색상인지 판단할 수 있습니다.

실습 아이디어: parse_hex_rejects_signs()를 읽고 #+f+f+f, ##0f0f0f, #0f0f0f가 각각 어느 조건에서 갈리는지 표로 적어 보세요. 성공값만 확인하는 테스트보다 잘못된 입력의 종류를 분류하는 테스트를 배울 수 있습니다.

6-2. 타입 설계: 숫자가 같아도 의미가 다르면 구별하기

crates/project/src/lib.rs의 id_type! 매크로는 ItemId, TrackId, ClipId 등을 각각 u64를 감싼 별도 타입으로 만듭니다. 같은 숫자 12라도 “원본 아이템 12”와 “타임라인 클립 12”는 다른 의미입니다.

용어
새 타입 패턴
struct ClipId(pub u64)처럼 기본 타입을 한 겹 감싸 의미를 부여하는 방법입니다. 함수가 ClipId를 받게 하면 TrackId를 실수로 넘기는 코드를 컴파일 단계에서 구별할 수 있습니다. 단, JSON 경계에서는 숫자를 올바른 타입과 실제 대상에 대응시키는 검사가 여전히 필요합니다.

실습 아이디어: 서점의 BookId와 MemberId를 만들어 같은 숫자 타입을 직접 쓰는 경우와 비교하세요. “메모리 표현이 같다”와 “업무상 같은 값이다”를 분리하는 연습입니다.

6-3. trait와 의존성 역전: 사용할 기능을 약속으로 받기

crates/media/src/lib.rs의 MediaSource는 프레임·오디오를 얻는 소스의 공통 약속입니다. 파일, 정지 이미지, 이미지 시퀀스, 합성 데모가 같은 인터페이스에 들어갑니다. crates/render/src/lib.rs::SourceProvider는 아이템 ID를 소스에 연결하는 방법을 추상화합니다.

또 crates/codecs/src/video.rs::VideoDecoder에는 decode(), flush(), reset() 등이 있습니다. register_video_decoder()는 새 디코더 팩토리를 목록 앞에 넣습니다. 운영체제 디코더를 먼저 시도하고, 지원 범위에 따라 소프트웨어 구현으로 돌아갈 수 있는 연결점입니다.

// VideoDecoder의 핵심 인터페이스 발췌
pub trait VideoDecoder: Send {
    fn decode(&mut self, sample: &[u8], pts: i64)
        -> Result<Vec<DecodedFrame>>;
    fn flush(&mut self) -> Vec<DecodedFrame>;
    fn reset(&mut self);
    // 원본에는 이름·랜덤 접근·초안 모드 등의 메서드도 있음
}

실습 아이디어: 렌더러가 실제 파일 대신 단색 소스를 받는 과정을 그리세요. 테스트를 위해 큰 동영상을 준비하는 대신, 재현 가능한 가짜 입력을 넣을 수 있는 이유가 드러납니다.

6-4. 동시성: 공유 읽기와 변경 요청을 구별하기

Arc<Project>는 변경되지 않는 스냅샷을 여러 읽기 작업이 함께 소유하게 합니다. 반면 MCP의 창 없는 Backend::Headless는 Arc<Mutex<Session>>입니다. 전자는 문서를 읽는 쪽의 수명 문제, 후자는 명령이 세션 상태를 바꾸는 순서 문제를 풉니다.

실습 아이디어: “재생이 문서 A를 읽는 중에 편집으로 문서 B가 생겼다”는 상황을 시간순으로 그려 보세요. A를 읽는 작업이 끝나기 전에 메모리가 해제되지 않는 이유와, 공유 문서를 직접 수정하지 않는 이유를 설명해 보세요.

6-5. 그래픽스: 좌표·색·투명도를 따로 생각하기

PlanLayer는 프레임과 좌표 변환 행렬, 불투명도, 혼합 모드를 함께 보관합니다. crates/geom의 변환과 crates/color의 색 변환은 서로 다른 문제입니다. 위치가 맞는데 색이 틀리는 오류와, 색은 맞는데 위치가 어긋나는 오류를 분리해 볼 수 있습니다.

실습 아이디어: 단색 사각형 두 개로 시작해 위치 이동, 반투명 겹치기, 색 공간 변환을 한 번에 하나씩 추가하세요. 차이를 설명할 수 있는 작은 입력이 복잡한 실제 영상보다 디버깅에 유리합니다.

6-6. 시스템 프로그래밍: 빠른 계산보다 메모리의 생애가 먼저다

crates/media/src/cache.rs::FrameCache는 프레임 개수가 아니라 바이트 예산을 기준으로 캐시를 관리합니다. 예산을 넘으면 오래 사용하지 않은 항목부터 내보내 목표 사용량을 예산의 90%로 낮춥니다. 서로 크기가 다른 프레임을 모두 “한 장”으로 세면 메모리 사용을 통제하기 어렵기 때문입니다.

실습 아이디어: 같은 10장의 프레임이라도 360p와 4K의 메모리가 얼마나 다른지 계산하고, “최대 10장” 캐시와 “최대 64 MiB” 캐시의 차이를 비교하세요. 문서에 성능 수치가 있더라도 입력 크기·동시 작업 수·캐시 조건을 함께 읽어야 합니다.

6-7. 검증과 저장: 규칙을 자동으로 확인하는 코드

xtask/src/main.rs::layers()는 아키텍처 규칙을 검사하고, crates/format/src/atomic.rs::atomic_write()는 같은 디렉토리의 임시 파일에 먼저 쓰고 동기화한 뒤 이름을 바꿉니다. 마지막 디렉토리 동기화는 최선 노력 방식으로 처리합니다. “저장 버튼을 만들었다”보다 더 깊은 데이터 보존 문제입니다.

좋은 학습 단위는 기능 이름이 아니라, 그 기능이 실패해도 지켜야 하는 불변식입니다. 색상은 유효한 형식이어야 하고, 자른 두 조각의 길이는 원래 길이를 보존해야 하며, 실패한 편집은 현재 문서를 망가뜨리지 않아야 합니다.

7하드웨어 / 시스템 요구사항

공식 선언, 코드의 예산, 학습을 위한 선택을 구별한다

커밋 기준 문서에는 모든 편집 상황을 포괄하는 CPU·RAM·VRAM 최소 사양표가 없습니다. 따라서 특정 메모리 용량을 공식 최소 사양으로 제시할 수 없습니다. 대신 실행 대상과 빌드 조건은 다음 파일에서 확인할 수 있습니다.

대상확인되는 조건읽을 근거
소스 빌드Rust stable ≥ 1.95, edition 2024. WASM 검사에는 해당 타깃 필요Cargo.toml, docs/contributing.md
macOS패키징 타깃은 11.0. Apple silicon·Intel 대상으로 구성packaging/macos/package.sh, packaging/macos/Info.plist.in
Windows소스 빌드에는 MSVC 도구. GPU 드라이버와 백엔드 선택이 실제 실행에 영향docs/contributing.md, apps/filmcraft/src/graphics.rs
LinuxC 도구 체인, 오디오·창 시스템 개발 라이브러리. CI는 Ubuntu 24.04.github/workflows/ci.yml의 패키지 목록
브라우저WASM과 그래픽·오디오 API. WebGPU에서 GPU 합성, WebGL2 경로에서는 CPU 합성docs/web.md, apps/filmcraft-web
선택적 도구FFmpeg·ffprobe는 비교 테스트용이며 일반 실행의 필수 코덱 프로그램이 아님docs/contributing.md, AGENTS.md

macOS의 11.0은 배포 설정값이며 모든 기기에서의 실행 보증과는 다릅니다. 또 기여 문서의 Linux 안내에는 GTK 3가 나오지만, 현재 CI는 ALSA·X11·Wayland·폰트·Vulkan 관련 패키지를 설치합니다. 환경 구성에서는 설명 문서의 날짜와 실제 워크플로를 함께 보아야 합니다.

7-1. 메모리는 해상도와 표현 형식에 따라 달라진다

RGBA의 네 채널을 각각 32비트 실수로 저장한다면 한 픽셀은 16바이트입니다. 압축 파일의 크기와 디코딩·합성 중 메모리 크기는 같지 않습니다. 아래는 프레임 한 장의 산술 계산입니다.

# 부가 자료구조를 제외한 RGBA f32 픽셀 버퍼
1920 × 1080 × 4 × 4 =  33,177,600 바이트 ≈ 31.6 MiB
3840 × 2160 × 4 × 4 = 132,710,400 바이트 ≈ 126.6 MiB

# 가로·세로를 각각 절반으로 줄이면 픽셀 수는 1/4
960 × 540 × 4 × 4 = 8,294,400 바이트 ≈ 7.9 MiB

crates/render/src/lib.rs의 Canvas는 실제 합성이 필요할 때까지 이미지 할당을 미룹니다. 하지만 효과의 중간 버퍼, 여러 레이어, 디코더 참조 프레임, GPU 텍스처가 추가될 수 있습니다. 위 값은 앱 전체 RAM 사용량도, GPU 경로의 텍스처 용량도 아닙니다.

crates/export/src/job.rs는 겹쳐 처리하는 프레임 묶음에 IN_FLIGHT_BUDGET = 1 << 29, 즉 512 MiB를 설정합니다. overlap_batch()는 그 안에 들어갈 묶음 크기를 계산하되 최소 한 프레임은 허용합니다. 이 예산은 해당 버퍼 묶음의 계산 기준이며 프로세스 전체 메모리의 강제 상한은 아닙니다.

학습을 시작할 때는 작은 해상도와 짧은 클립으로 계산을 이해한 뒤, 해상도·레이어·동시 작업을 하나씩 늘리세요.

7-2. CPU 대체 경로와 브라우저 제약

FILMCRAFT_CPU_COMPOSITE=1은 데스크톱 미리보기의 GPU 합성을 끕니다. 내보내기의 GPU 사용은 별도 설정입니다. 창을 띄우는 그래픽 장치 초기화까지 없애는 설정은 아니므로 “GPU가 전혀 필요 없다”로 해석하면 안 됩니다. 창 없는 CLI와 창을 띄우는 데스크톱 앱도 요구 조건이 다릅니다.

docs/web.md 기준 웹 빌드는 단일 스레드입니다. 프레임 작업은 FrameServer::pump, 내보내기는 Session::pump_jobs를 통해 조금씩 진행하며, WebAudio와 OPFS가 각각 소리와 복구 저장을 맡습니다. 데스크톱 스레드를 그대로 브라우저에서 실행하는 구조는 아닙니다.

성능 수치를 읽는 법
문서 속 fps를 내 컴퓨터의 보장값으로 쓰지 않기

docs/performance.md의 초기 측정 환경은 Apple M4 Pro, 14코어, 48 GB이며 높은 동시 부하도 명시합니다. README의 디코딩 속도나 내보내기 시간은 프로젝트가 보고한 조건별 값입니다. 해상도·코덱·효과·캐시·빌드 프로필이 달라지면 결과도 달라집니다.

8직접 해볼 수 있는 실습 과제

작은 입력, 분명한 기대 결과, 눈으로 확인할 수 있는 산출물

아래 과제는 읽기와 계산에서 시작해 작은 연습 모델로 확장합니다. 기존 코드를 통째로 실행하지 않아도 핵심 원리를 검증할 수 있습니다. 난이도보다 어떤 조건이 맞으면 성공인지를 먼저 정하고 진행하세요.

과제 1 · 입력 검증

색상 문자열의 통과·실패 표 만들기 입문

읽을 곳: crates/color/src/lib.rs::parse_hex(), parse_hex_rejects_signs().

할 일: 아래 입력의 결과와 실패 이유를 함수의 검사 순서대로 적습니다. #를 모두 제거하는 구현과 하나만 제거하는 구현의 차이도 설명하세요.

입력예상 결과설명할 지점
#0f0f0f성공6자리, 알파는 1.0
0f0f0f80성공8자리, 알파는 128/255
#+f+f+f실패길이만 맞아도 부호는 16진수 문자가 아님
##0f0f0f실패남은 두 번째 #이 문자 검사에서 걸림
#fff실패이 함수는 3자리 축약형을 받지 않음

완료 기준: “잘못됐다” 대신 문자 집합·길이·접두사 중 어느 규칙인지 구분할 수 있습니다.

과제 2 · 정확한 시간

프레임과 오디오 샘플의 공통 눈금 찾기 입문

읽을 곳: crates/time/src/lib.rs::FrameRate::frame_duration()와 Tick::from_units().

할 일: 아래 독립적인 Python 계산을 읽고, 나머지가 0인 이유를 설명합니다. 1,000프레임 뒤 시각과 48 kHz 샘플 48,000개 뒤 시각도 계산해 보세요.

ticks_per_second = 254_016_000_000
numerator = ticks_per_second * 1001
assert numerator % 30000 == 0
frame_ticks = numerator // 30000
sample_ticks = ticks_per_second // 48000
assert sample_ticks * 48000 == ticks_per_second
print(frame_ticks, sample_ticks)

완료 기준: 한 프레임은 8,475,667,200 Tick, 1,000프레임은 8,475,667,200,000 Tick이라고 설명하고, 화면의 소수점 초 표시와 내부의 정수 시각을 구별합니다.

과제 3 · 명령에서 데이터까지

자르기 한 번의 변경 명세 작성하기 중급

읽을 곳: crates/engine/src/commands.rs의 timeline.razor 등록부와 crates/edit/src/lib.rs::split_track_at().

조건: 클립은 타임라인 10초부터 6초 길이로 놓여 있고, 원본 시작점은 5초입니다. 배속은 1, 역재생·프레임 고정·전환·링크는 없다고 정합니다. 13초에서 자르면 어떻게 될까요?

자르기 전 타임라인: 10초 [--------------------] 16초 원본 구간: 5초 ............. 11초 13초에서 자른 뒤 왼쪽 클립: 10초 [---------] 13초 원본 5~8초 오른쪽 클립: 13초 [---------] 16초 원본 8~11초

완료 기준: 두 클립의 길이는 각각 3초이고 오른쪽 원본 시작점은 8초입니다. 원본 아이템 참조는 유지되지만 오른쪽 클립 ID는 새로 할당됩니다. 왼쪽·오른쪽 경계에서 자르면 왜 내부 분할이 없는지도 설명하세요.

확장: speed = 2일 때 오른쪽 source_in이 얼마나 이동하는지 비교하되, 타임라인 길이와 원본에서 소비하는 길이를 혼동하지 마세요.

과제 4 · 실패해도 보존되는 상태

작은 문서 모델에 실행 취소 붙이기 중급

읽을 곳: Session::edit(), edit_merged(), undo(), redo().

할 일: 연습용 Rust 프로그램에 제목과 숫자 목록만 가진 문서를 만듭니다. 복제본에서 숫자를 추가하고, 음수 입력이면 오류를 반환하며, 성공했을 때만 현재 문서와 교체합니다.

  1. 정상 편집 두 번 후 undo·redo를 한 번씩 적용합니다.
  2. 복제본을 일부 변경한 뒤 의도적으로 오류를 반환합니다.
  3. 같은 조절 동작을 여러 번 보내도 undo 한 단계가 되도록 합치기를 추가합니다.

완료 기준: 실패한 요청 전후에 현재 문서와 이력 길이가 같고, 새 편집이 성공하면 redo가 비워집니다. 모든 값을 공유할 때와 실제 복제할 때의 비용도 구별합니다.

과제 5 · 서로 다른 구현 비교

CPU·GPU 결과 비교 계획 세우기 고급

읽을 곳: crates/gpu/src/tests.rs::yuv_code_range_gpu_matches_cpu(), crates/render/src/plan.rs::execute_cpu().

할 일: 단색, 회색 단계, 반투명 겹치기, 위치 이동을 각각 별도 입력으로 설계합니다. 같은 프레임 계획을 두 경로에 넣고 출력 크기, 최대 채널 오차, 차이가 생긴 좌표를 비교하는 기록 양식을 만드세요.

완료 기준: 해당 YUV 코드 범위 테스트의 허용 오차 1이 무엇을 뜻하는지 설명합니다. 다른 효과에도 같은 값을 무조건 적용하지 말고 비교 대상의 표현 형식에 맞춰 기준을 정합니다. GPU 장치가 없어 테스트를 건너뛴 경우는 비교 성공과 구별해 기록합니다.

실습의 성공 기준을 “화면이 나왔다”에서 “어떤 입력에 어떤 상태와 결과가 남는지 설명할 수 있다”로 바꿔 보세요.

9관련 기술 심화 학습 로드맵

Rust 기초에서 시스템 설계까지, 작은 증거를 남기며 전진하기

주차는 진도 배분의 예시입니다. 하루에 읽을 파일 수를 정하기보다, 그 주의 질문에 답하는 산출물을 하나씩 남기세요. 프로그래밍을 막 시작했다면 1~2주차를 더 길게 잡고 코덱 내부 수학은 뒤로 미뤄도 됩니다.

주차핵심 질문 / 기술읽을 코드남길 산출물
1주차오류와 정상값을 어떻게 구별할까?
Rust의 Option·Result·패턴 매칭
crates/color/src/lib.rs의 parse_hex()유효·무효 입력 표와 새 타입을 쓴 작은 예제
2주차영상의 시간은 어떻게 셀까?
정수·분수·반열린 구간
crates/time/src/lib.rs24·30000/1001 fps와 48 kHz의 변환 표
3주차편집은 어떤 자료구조를 바꿀까?
문서 모델과 불변식
crates/project/src/lib.rs, crates/edit/src/lib.rs자르기 전후 클립 명세, 길이 보존 설명
4주차실패와 실행 취소는 어디서 처리할까?
스냅샷·명령·트랜잭션
crates/engine/src/lib.rs, crates/engine/src/commands.rs작은 문서의 undo·redo 모델과 실패 사례
5주차파일에서 한 프레임을 어떻게 얻을까?
컨테이너·코덱·trait·캐시
crates/media/src/lib.rs, crates/codecs/src/video.rs가져오기부터 프레임 요청까지의 호출 지도
6주차빠른 구현은 어떻게 검증할까?
선형광·좌표 변환·GPU 비교
crates/render/src/plan.rs, crates/gpu/src/tests.rs작은 합성 입력 세트와 오차 기록표
7주차같은 엔진을 다른 환경에 연결하려면?
MCP·WASM·OS 경계·CI
crates/automation/src/server.rs, apps/filmcraft-web, xtask입출력 경계도와 자동 검사할 규칙 세 가지

표에서 crates/color/부터 crates/automation/까지는 공통 기능을 나눈 crates/ 아래 라이브러리 패키지입니다. 매주 공식 문서에서 개념 하나를 읽고, 대응하는 함수와 테스트 하나를 찾아 연결하면 됩니다.

9-1. 관심 분야에 따라 갈라지는 다음 단계

다음 단계로 넘어가는 기준은 파일을 다 읽었는지가 아니라, 작은 입력의 결과를 스스로 예측하고 근거 함수를 찾을 수 있는지입니다.

공부 방법

대형 저장소는 첫 장부터 끝까지 읽는 소설보다 도시 지도에 가깝습니다. 오늘의 목적지를 하나 고르고, 지나간 길에 함수 이름과 상태 변화를 적어 두세요. 같은 길을 UI·CLI·MCP에서 다시 만나면 구조가 머릿속에서 이어지기 시작합니다.

10핵심 키워드 사전

소스에서 다시 만날 단어를 쉬운 말로 정리한다
용어쉬운 설명
비선형 편집원본의 원하는 구간을 자유롭게 배치하는 편집 방식. 순서표와 원본을 따로 다룹니다.
Sequence영상 크기·프레임률과 여러 트랙을 묶은 하나의 편집 타임라인입니다.
TrackItem원본 아이템을 타임라인에 놓은 인스턴스. 시작점·길이·효과를 따로 가집니다.
source_in클립이 참조하는 원본 구간의 시작 시각입니다. 역재생은 이 구간의 끝쪽부터 보여 줍니다.
Tick영상과 오디오의 시각을 함께 세는 정수 눈금입니다.
유리수 프레임률30000/1001처럼 분자와 분모로 표현하는 초당 프레임 수입니다.
반열린 구간시작은 포함하고 끝은 제외하는 구간. 이웃한 클립의 경계를 겹치지 않게 표현합니다.
컨테이너영상·음성·시간 정보 등을 담는 파일 구조. MP4·MOV·Matroska 등이 해당합니다.
코덱영상·음성을 압축하거나 압축된 데이터를 복원하는 규칙과 구현입니다.
디코딩 / 인코딩압축 데이터를 프레임·샘플로 복원하는 일 / 프레임·샘플을 압축 데이터로 바꾸는 일입니다.
멀티플렉싱(muxing)인코딩된 영상·음성 패킷과 시간 정보를 하나의 컨테이너에 묶는 작업입니다.
GOP서로 참조할 수 있는 압축 영상 프레임의 묶음. 원하는 한 장을 얻으려 앞 프레임부터 풀어야 할 수 있습니다.
PTS프레임을 보여 줄 시각. 디코더에 데이터를 넣는 순서와 화면에 보여 주는 순서는 다를 수 있습니다.
trait여러 구현이 지켜야 하는 기능 약속. 파일 소스나 디코더를 바꿔 끼울 때 사용합니다.
Arc여러 작업이 같은 값을 함께 소유하도록 돕는 참조 계수 포인터. 이것만으로 내부 수정이 안전해지지는 않습니다.
Mutex공유 상태에 한 번에 하나의 작업만 접근하도록 순서를 조정하는 잠금입니다.
스냅샷어느 시점의 문서 상태. 이후 편집이 생겨도 읽던 상태를 유지하는 데 사용합니다.
트랜잭션여러 변경을 성공 시 함께 반영하고 실패 시 반영하지 않는 처리 단위입니다.
명령 레지스트리명령 이름·입력 설명·실행 조건·함수를 모아 둔 목록입니다.
불변식어떤 기능을 실행해도 계속 참이어야 하는 규칙. 예를 들어 트랙의 클립 정렬이나 길이 조건입니다.
선형광빛의 양을 기준으로 계산하는 색 표현. 합성과 색 보정의 계산 공간을 이해할 때 중요합니다.
WGSL / wgpuGPU가 실행할 계산을 적는 언어 / 여러 그래픽 백엔드 위에서 GPU 작업을 구성하는 Rust API입니다.
기준 구현다른 최적화 구현의 결과와 비교할 출발점. 기준 구현 자체의 오류 가능성도 별도로 검증해야 합니다.
LRU 캐시최근 덜 사용한 항목부터 비우는 임시 저장소입니다. FilmCraft의 프레임 캐시는 바이트 예산도 고려합니다.
WASM / OPFS브라우저에서 실행하는 컴파일 코드 형식 / 웹 출처별 전용 파일 저장소입니다.
MCP / 연결 모드도구 호출을 연결하는 프로토콜 / 호출을 실행 중인 데스크톱 앱에 전달하는 방식입니다.
FFI다른 언어·플랫폼 API와 연결하는 경계. 이 프로젝트에서는 OS 미디어 API를 별도 크레이트에 가둡니다.
원자적 저장완성된 새 파일을 준비한 후 교체해, 저장 도중의 불완전한 파일이 정본이 될 위험을 줄이는 방법입니다.

11참고 링크

움직이는 최신 상태와, 이 글이 설명하는 커밋을 구별한다

11-1. 공식 저장소와 프로젝트 문서

11-2. 본문의 핵심 소스 바로가기

주제소스 / 확인할 함수
의존성Cargo.toml · Cargo.lock
명령·상태crates/engine/src/lib.rs의 execute()·edit()·undo() / crates/engine/src/commands.rs의 CommandSpec
문서·자르기crates/project/src/lib.rs / crates/edit/src/lib.rs의 split_track_at()
시간·입력crates/time/src/lib.rs / crates/color/src/lib.rs의 parse_hex()
GPU 계획·비교crates/render/src/plan.rs / crates/gpu/src/tests.rs
내보내기crates/gpu/src/export_renderer.rs / crates/export/src/job.rs
MCP·제어 채널crates/automation/src/server.rs / apps/filmcraft/src/control_server.rs
저장·품질 검사crates/format/src/atomic.rs / xtask/src/main.rs / CI 설정

11-3. 관련 기술의 공식 학습 자료

외부 기술 문서의 기본 페이지는 최신 버전을 보여 줄 수 있습니다. 이 글의 API와 의존성 버전을 맞춰 읽으려면 Cargo.lock의 버전을 기준으로 선택하세요.

현재 상태 (2026-10-23 확인)

항목상태기준
저장소 / 주 언어storytold/filmcraft / Rust2026-10-23 GitHub 기준
스타 / 포크15,809 / 6,6302026-10-23 GitHub 기준
생성일2026-09-30GitHub 저장소 메타데이터
최근 push2026-10-23GitHub 저장소 메타데이터
최신 릴리스v0.12.0 · 2026-10-23GitHub 릴리스 메타데이터
분석 기준 커밋abf44fd · 2026-10-22
Reject non-hex characters in colour hex parsing (#2600)
커밋 원문 시각은 -0600. 한국 시각으로는 10월 23일
패키지 규모라이브러리 등 50개 + 앱 3개 + xtask커밋 기준 매니페스트 개수
코드 규모Rust 407,079줄 / WGSL 1,422줄Git 추적 파일의 물리적 줄 수, 테스트·주석·빈 줄 포함
GitHub 라이선스 표기Apache-2.02026-10-23 GitHub 기준
소스의 공통 라이선스MIT OR Apache-2.0Cargo.toml, LICENSE-MIT, LICENSE-APACHE
현재 제품 단계alpha커밋 기준 README·ROADMAP. v0.12.0이라는 번호와 제품 안정 단계는 별개
쓰기 전에 알아둘 한계
기능 설명·구현 존재·실제 호환성을 같은 것으로 보지 않기

이 글의 소스 기준은 abf44fd입니다. 최신 릴리스의 모든 파일이 이 커밋과 같다는 뜻은 아닙니다. 제품은 alpha이며 실제 제작 파일의 가져오기·재생·교환·내보내기 결과는 해당 환경에서 따로 확인해야 합니다. README의 속도와 준비도 수치는 프로젝트의 보고값·자체 평가입니다.

문서에는 갱신 시차가 있습니다. README의 “무제한 undo”와 달리 기본 이력 한도는 200이며, CPU 전용처럼 서술된 내보내기에도 GPU 구현이 있습니다. “camera RAW 미지원”이라는 안내와 달리 crates/dng와 crates/media/src/still/dng.rs에는 DNG·CinemaDNG 연결이 있습니다. 이것을 모든 카메라 RAW 지원으로 확대해서 읽으면 안 됩니다.

docs/gaps.md의 “PR 테스트 CI 없음”이라는 서술과 달리 현재 .github/workflows/ci.yml은 PR에서 품질 검사를 호출합니다. 설정의 존재는 모든 검사 실행의 성공을 뜻하지 않습니다. ROADMAP.md의 45개 크레이트·약 34.3만 줄도 현재 트리의 50개 크레이트·Rust 약 40.7만 줄과 기준 시점이 다릅니다.

README 기준 .prproj 직접 가져오기와 VST3·Audio Units·OpenFX 호스팅은 아직 지원하지 않습니다. 하드웨어 가속과 코덱 지원은 운영체제·장치·프로파일에 따라 달라집니다. 자동화 제어 채널은 루프백에 연결하고 64자리 16진수 토큰을 요구하며, 토큰이 없으면 리스너를 열지 않습니다.

GitHub의 Apache-2.0 표기와 달리 공통 패키지 선언은 MIT OR Apache-2.0입니다. 자산은 개별 출처·라이선스를 가지며, docs/brand/LICENSE-brand.txt의 브랜드 표시는 별도 조건입니다. 코드의 라이선스만 보고 모든 이미지·폰트·이름에 같은 조건을 적용하지 마세요.