Skip to content

Repository files navigation

Skippa

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.

Quickstart: local web app

Requirements: Python 3.12+, uv, and ffmpeg/ffprobe on PATH.

uv sync
uv run python scripts/check_system_deps.py
uv run skippa serve

skippa 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:

  1. 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.
  2. Watch analysis progress over SSE. Cancelled jobs write no partial result and can be restarted.
  3. Pick 90, 120, 140 BPM or enter a positive BPM. Skippa uses a quarter-beat grid by default.
  4. 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.
  5. Items you actually inspect and leave unchanged are recorded as reviewed; merely rendering an offscreen item is not.
  6. 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.

What v2 does

  • 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 atempo output is measured and cached before a final schedule can be rendered. Predicted lengths never reach render.
  • One timeline authority: TimeMap owns 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.

Optional script alignment

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 align

The 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.

CLI (secondary surface)

uv run skippa --help
uv run skippa serve --no-browser --port 0
uv run skippa init tests/fixtures/synthetic_speech.wav --json

The 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
}

MCP (secondary surface)

Run the stdio server:

uv run skippa mcp

MCP 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.

Current architecture constraints

  • 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.

Profiles and learning

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.

Precision and deterministic verification

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.

Development

uv run pytest -q
uv run ruff check .
uv run basedpyright
cd ui && bun run build
uv run python scripts/check_docs.py

The 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.

License

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으로 검증한 뒤 원장에는 항상 원본 샘플 좌표만 기록합니다.

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages