Local, beat-aware narration editing with sample-accurate cuts, Korean subtitles, and an explicit style-learning loop.
Skippa removes dead air without flattening rhetorical pauses. Its acoustic refiner protects Korean codas and fricative tails; its optional beat scheduler places eligible phrase starts on a BPM grid using silence first and bounded per-segment stretch only as a last resort. Anchors that cost too much simply pass. Every correction is an append-only ledger event.
Requirements: Python 3.12+, uv, and ffmpeg/ffprobe on
PATH.
uv sync
uv run python scripts/check_system_deps.py
uv run skippa serveskippa serve prints the actual URL, opens it in your browser, and serves only on
127.0.0.1. Use --no-browser for a terminal-only launch or --port 0 to ask the OS for
a free port. If a requested fixed port is occupied, Skippa falls back to a free loopback
port and prints that URL.
In the browser:
- Drop an audio/video file onto the landing page. Browser drag/drop provides bytes, so the file crosses the loopback connection in chunks; it never leaves this machine.
- Watch analysis progress over SSE. Cancelled jobs write no partial result and can be restarted.
- Pick 90, 120, 140 BPM or enter a positive BPM. Skippa uses a quarter-beat grid by default.
- Audition the edited preview, compare original/edited A/B, drag a boundary, align/unalign or pin an anchor, then press re-align. Pins partition the solve and never move.
- Items you actually inspect and leave unchanged are recorded as reviewed; merely rendering an offscreen item is not.
- Open the profile report and explicitly check any proposed deltas you accept. Applying a delta writes a new profile version; it never overwrites the source profile.
The web app is the primary product surface. CLI and MCP remain supported for automation and v1 workflows.
- Beat grid: exact origin-based arithmetic; no accumulated floating-point beat drift.
- Partial alignment: nearest beat only. Far or infeasible anchors pass instead of forcing unnatural timing.
- Re-align with pins: source-native ledger events are folded, pins become hard constraints, and only unpinned windows are re-solved.
- Measured stretch: ffmpeg
atempooutput is measured and cached before a final schedule can be rendered. Predicted lengths never reach render. - One timeline authority:
TimeMapowns source/output conversion for render, subtitles, preview, reports, and seeking. - Real preview: edited-timeline WAV chunks are cached by schedule hash; video preview uses the source video plus the same seek map.
- Learning: adjust, accept/reject, unalign, pin, and reviewed events carry beat context. Reports are deterministic and proposals stay consent-gated.
- v1 compatibility: projects without a grid still use the v1 tempo engine, existing CLI commands and MCP tools remain registered, and the v1 golden corpus is unchanged.
Skippa works without a script. To align user-provided Korean script text and improve corrected transcript text, sentence hints, and anchor candidates:
uv sync --extra alignThe extra installs the CPU MMS_FA runtime and uroman; it is not part of the base runtime.
On first real use, torchaudio downloads a 1,262,047,414-byte MMS checkpoint through
torch.hub. Set TORCH_HOME to choose the model cache location. The downloaded MMS weights
are CC-BY-NC 4.0, not MIT, and are not bundled with or licensed as part of Skippa. Review
the MMS license
before enabling this optional feature. Without the extra, the web capability is disabled and
the script endpoint returns 501 with the install hint; the rest of Skippa is unaffected.
The local API contract used by the transcript panel is exact: POST /api/projects/{project_id}/script takes {"text":"<non-empty script, max 1,000,000 chars>", "language":"ko"} and returns HTTP 202 {"stage":"align","running":true}. Progress is read
from /status or SSE /events. Once done, GET /api/projects/{project_id}/script returns
media_sha256, corrected TranscriptDoc, (word_index, old, new) corrections, sentence
hints, unaligned-word count, and string metadata. An artifact is hidden while replacement is
in flight or when its media hash is stale.
uv run skippa --help
uv run skippa serve --no-browser --port 0
uv run skippa init tests/fixtures/synthetic_speech.wav --jsonThe existing v1 chain is preserved:
skippa init <media>
skippa transcribe <project>
skippa analyze <project>
skippa cut <project>
skippa review <project>
skippa render <project> -o out.wav
skippa export-xml <project> -o out.xml
skippa subtitles <project> -o out.srt
skippa learn <project>
skippa mcp
serve opens the upload-first app. review opens one existing project directly. Commands
that support --json emit one JSON object and no human prose. For example, init --json
returns:
{
"project_dir": "/absolute/path/synthetic_speech.skippa",
"project_id": "synthetic_speech",
"media_path": "/absolute/path/synthetic_speech.wav",
"created": true,
"sample_rate": 48000,
"channels": 1,
"duration_seconds": 9.64,
"duration_samples": 462720,
"has_video": false
}Run the stdio server:
uv run skippa mcpMCP sends compact JSON metadata only: no PCM, waveform, preview chunk, or rendered media is placed in an agent context. The original 12 v1 tools remain, with three additive v2 tools:
| Tool | Exact inputs | JSON result |
|---|---|---|
skippa_set_grid |
project: str, bpm: float (0,1000], subdivision: int=1 (1..64) |
schedule, timemap, provisional, reviewed_cut_ids |
skippa_schedule |
project: str, optional alignment_strength: weak|medium|strong |
same authoritative schedule response |
skippa_pin |
project, anchor_id, action, optional value_sample or output_sample |
fresh schedule response after the ledger append |
skippa_pin.action is exactly one of set_anchor, unalign, pin, unpin,
mark_reviewed. pin and set_anchor require exactly one coordinate; the other actions
forbid coordinates. An output_sample is mapped through the current TimeMap and rejected
if it cannot round-trip. The event stored in ledger.jsonl is always source-native.
Repeated mark_reviewed calls are idempotent.
The complete tool flow is:
skippa_open_project -> skippa_transcribe -> skippa_propose_cuts
-> skippa_set_grid -> skippa_schedule
-> skippa_pin (user-directed anchor controls)
-> skippa_review (human preview/A-B/review flow)
-> skippa_get_ledger_report -> skippa_apply_profile_deltas
-> skippa_render / skippa_subtitles
skippa_review(project, port=0) is the preview/review bridge: it returns project, the
actual loopback url, detached child pid, project_dir, and a stop note. The browser reads
GET /preview/map (schedule_hash, sample rate/durations, chunk geometry, TimeMap pieces,
removed spans) and Range-capable GET /preview/chunk/{index}; these media bytes never cross
MCP.
skippa_get_ledger_report returns the original proposals list plus:
{
"beat": {
"profile_has_beat_section": true,
"proposals": [
{
"knob": "beat.giveup_abs_cap_ms",
"current": 150.0,
"observed_median": 120.0,
"n": 8,
"proposed": 120.0
}
]
}
}The example is deterministic contract data, not a promise that an untouched ledger produces
a proposal. Observation floors apply; an empty list honestly means insufficient evidence.
skippa_apply_profile_deltas requires a non-empty explicit list. Numeric values must be
finite JSON numbers within knob-specific bounds; beat.alignment_strength accepts the three
named strengths. A successful call returns profile_path, profile_id, profile_version,
and the exact applied list.
See docs/codex-setup.md for host configuration and AGENTS.md
for the agent operating contract.
- Local-only: no cloud, auth, accounts, OAuth, telemetry, or non-loopback web serving.
- Browser upload is chunked loopback I/O; MCP never transports media.
- No BPM extraction from music files. BPM is a preset or explicit number.
- Quarter-beat subdivision is the launch default; phase is frozen at sample 0.
- Stretch is per segment, bounded by the profile, pitch-preserving
atempo, and measured. There is no global speed change. - XMEML cannot represent v2 per-clip retiming here. Exporting a stretched schedule fails loudly and lists the unsupported operations.
- Video frame quantization takes precedence over exact beat position; reports expose the resulting error, bounded to one frame.
- Script alignment is optional and coarse. Skippa's acoustic refiner remains the boundary precision authority.
- Profiles never mutate silently. Every accepted delta creates a new version.
Three profiles ship:
tight_korean_v1: v1 podcast-tight silence editing.relaxed_v1: v1, with more air.beat_korean_v1: v2 beat defaults, including give-up distance, alignment strength, stretch caps, and context-specific gap floors/preferences.
V1 profile proposals require at least five consistent observations and a meaningful delta.
Beat give-up proposals require eight observations; strength drift requires ten. Reviewed
snapped anchors count as agreement. Fitting is pure and deterministic; application is a
separate explicit operation that writes <profile_id>_v<n>.toml atomically.
The beat sample is computed from the origin as
round(n * 60 * sample_rate / (bpm * subdivision)). At 120 BPM and 48 kHz, quarter-note
beats are exactly 0, 24000, 48000, 72000, ... samples. A snapped schedule anchor equals its
selected beat sample exactly; rendered audio is allowed at most one splice-rounding sample.
Same inputs, measured stretch document, profile, and ledger produce byte-identical schedule
JSON.
Measured v1 results retained by v2:
- Synthetic boundary onset error: median/max 1.23 ms.
- Compensated output gap error: within 0.604 ms of the requested target.
uv run pytest -q
uv run ruff check .
uv run basedpyright
cd ui && bun run build
uv run python scripts/check_docs.pyThe FCP7 xmeml exporter is parser-verified with OpenTimelineIO, not manually certified in Premiere Pro or DaVinci Resolve. Subtitle burn-in needs an ffmpeg build with libass; SRT/ASS sidecars work without it.
Skippa source is MIT. See LICENSE. Optional MMS model weights have their own CC-BY-NC 4.0
license as described above.
Skippa는 로컬에서 동작하는 내레이션 공백/박자 편집기입니다. 기본 사용법은 다음과 같습니다.
uv sync
uv run python scripts/check_system_deps.py
uv run skippa serve브라우저에 파일을 놓으면 바이트가 이 컴퓨터 안의 loopback 연결로만 청크 업로드됩니다. 서버는
127.0.0.1에만 열리며 파일은 외부로 나가지 않습니다. 분석이 끝나면 BPM(90/120/140 또는 직접
입력)을 고르고, 실제 편집 결과를 재생하면서 A/B 비교, 경계 드래그, 앵커 정렬/해제/핀 고정,
재정렬을 할 수 있습니다. 핀은 절대 움직이지 않고, 너무 비싼 앵커는 억지로 맞추지 않고
통과시킵니다.
모든 수정과 "확인했지만 그대로 둠"은 원장에 기록됩니다. 프로필 보고서는 충분한 관측이 있을 때만 변경을 제안하며, 체크해서 승인한 항목만 새 버전 프로필로 저장됩니다. 기존 프로필을 덮어쓰거나 자동으로 학습하지 않습니다.
대본 정렬은 선택 기능입니다. uv sync --extra align으로 설치하며 첫 사용 시 약 1.262GB MMS
가중치를 TORCH_HOME 캐시에 받습니다. 이 가중치는 Skippa의 MIT가 아니라 CC-BY-NC 4.0입니다.
선택 기능이 없어도 전사, 박자 정렬, 미리듣기, 렌더, 자막, 학습 기능은 모두 동작합니다.
CLI/MCP는 자동화용 보조 인터페이스입니다. 기존 v1 명령과 12개 MCP 도구는 그대로 유지되고,
MCP에 skippa_set_grid, skippa_schedule, skippa_pin이 추가됩니다. 출력 좌표로 요청해도 서버가
TimeMap으로 검증한 뒤 원장에는 항상 원본 샘플 좌표만 기록합니다.