🎬 Silenci

March 31, 2026 Β· View on GitHub

Silenci logo

🎬 Silenci

Automatically remove silence from videos and generate perfectly synced subtitles

Drop a video β†’ AI detects & cuts silence β†’ Export to Final Cut Pro with word-level subtitles

macOS Apple Silicon License Stars

ν•œκ΅­μ–΄ λ¬Έμ„œ


Waveform before and after silence removal

Why Silenci?

Most silence-removal tools split audio by time, which cuts words in half. Silenci uses a 2-Pass ASR approach β€” first transcribe, then split only at word boundaries. No mid-word cuts. Ever.

Other toolsSilenci
Split methodTime-based β†’ words get choppedWord-boundary β†’ clean cuts
SubtitlesSeparate tool neededBuilt-in, word-level synced
Runs onCloud / GPU server100% local on your Mac
CostSubscription / API feesFree & open source
PrivacyUpload to cloudOffline β€” nothing leaves your Mac

✨ Features

πŸ”‡ Smart Silence Removal

  • Silero VAD for precise speech detection
  • Automatic silence removal β†’ compact timeline
  • FCPXML output (import directly to Final Cut Pro)
  • Multi-video merge support

πŸ—£οΈ AI Speech Recognition

  • Qwen3-ASR β€” high-quality speech-to-text (0.6B / 1.7B)
  • Qwen3-ForcedAligner β€” word-level timestamps
  • Multi-language: Korean Β· English Β· Japanese Β· Chinese
  • MLX 8-bit quantized β€” Apple Silicon optimized

βœ‚οΈ Word-Level Subtitle Splitting

  • Split at sentence endings & punctuation
  • Timestamps synced to exact word boundaries
  • FCPXML inline titles + iTT captions, SRT formats
  • Customizable font size & max characters per line

πŸ”„ FCPXML Retranscribe

  • Import edited FCPXML from Final Cut Pro
  • Re-transcribe subtitles with updated clip structure
  • Handles reordered & overlapping clips correctly
  • Language & model selection per retranscribe run

πŸ“± Two Interfaces

  • macOS native app β€” drag & drop, real-time preview
  • CLI β€” scriptable, automation-friendly

🌐 Localization

  • App UI: Korean Β· English Β· Japanese Β· Chinese
  • In-app language selector (independent of system locale)
  • Speech recognition for 4+ languages

πŸ”¬ How It Works β€” 2-Pass ASR Pipeline

Processing pipeline diagram

Most silence-removal tools split audio by fixed time windows before running ASR. This causes words to be cut in half at chunk boundaries. Silenci solves this with a 2-pass approach:

Pass 1:  VAD β†’ chunk by silence gaps (≀30s) β†’ ASR + ForcedAligner β†’ word-level timestamps
Pass 2:  Split only at word end_time boundaries β†’ never cuts mid-word

Detailed Pipeline

StepComponentInputOutput
1ffprobeVideo filefps, resolution, duration
2ffmpegVideo file16kHz mono WAV
3Silero VADWAV audioSpeech segments [{start, end}, ...]
4SplitSpeech segments≀30s chunks (split at silence gaps, not mid-speech)
5Qwen3-ASRAudio chunkTranscribed text
6Qwen3-ForcedAlignerAudio + textWord-level [{text, start, end}, ...]
7Word mergeAll wordsFull word list with absolute timestamps
8Segment splitWord listSegments split at end_time boundaries
9Subtitle splitSegmentsSubtitle chunks by punctuation/endings

When splitting at step 8, the algorithm prefers the largest silence gap between words, producing natural sentence-like segments.


πŸ—οΈ Architecture

Architecture diagram

Swift ↔ Python Bridge

The app uses a dual-process architecture: a Swift frontend communicates with a Python subprocess via JSON-RPC 2.0 over stdin/stdout pipes.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          stdin (JSON)          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Swift macOS App    β”‚  ──── {"method":"analyze"} ──→ β”‚  Python Subprocess   β”‚
β”‚                      β”‚                                β”‚                      β”‚
β”‚  β€’ SwiftUI Views     β”‚  ←── {"result": segments} ──── β”‚  β€’ silence_cutter/   β”‚
β”‚  β€’ PythonBridge      β”‚                                β”‚    server.py         β”‚
β”‚  β€’ ExportService     β”‚  ←── {"method":"progress"} ──  β”‚  β€’ Silero VAD        β”‚
β”‚  β€’ PythonEnvironment β”‚        (notifications)         β”‚  β€’ Qwen3-ASR (MLX)   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          stdout (JSON)         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why this architecture?

  • Isolation: Python ML stack (PyTorch, MLX) runs in a separate process β€” crashes don't take down the UI
  • Streaming progress: JSON-RPC notifications push real-time progress (VAD %, ASR chunk N/M, model download bytes)
  • No FFI overhead: No ctypes/cffi bindings needed β€” just line-delimited JSON
  • Cancelable: Swift can kill the Python process at any time for instant cancellation

JSON-RPC Protocol

// Request (Swift β†’ Python)
{"id": 1, "method": "analyze", "params": {"video_path": "/path/to/video.mp4", "language": "English"}}

// Progress notification (Python β†’ Swift, no id)
{"method": "progress", "params": {"phase": "analyze", "percent": 45, "detail": "Transcribing (12/26)"}}

// Model download notification
{"method": "progress", "params": {"phase": "model_download", "percent": 67, "detail": "1.2 GB / 1.7 GB"}}

// Response (Python β†’ Swift)
{"id": 1, "result": {"segments": [...], "video_info": {"fps": 23.976, "width": 1920, "height": 1080}}}

Auto-Install System

On first launch, PythonEnvironment.swift handles a fully automated setup chain:

App launch
  β†’ Check Homebrew     (not found? β†’ install via official script)
  β†’ Check Python3      (not found? β†’ brew install python@3)
  β†’ Check ffmpeg       (not found? β†’ brew install ffmpeg)
  β†’ Create venv        (~/Library/Application Support/Silenci/venv/)
  β†’ pip install        (torch, mlx-audio, silero-vad, soundfile, numpy, soynlp)
  β†’ Write version stamp (.sc-version)
  β†’ Ready βœ…

Subsequent launches skip installation if the version stamp matches. Bumping envVersion in code forces a clean reinstall.


πŸ–₯️ macOS App

Native SwiftUI app β€” load a video, configure settings, analyze, edit, and export in one window.

πŸ“‹ Analysis Settings

Analysis settings dialog

πŸ“Š Real-time Progress

Analysis progress view

βœ‚οΈ Word-level Editing

Main editing screen with word-level editing

App Features

FeatureDescription
🎬 Load videoDrag & drop or File β†’ Open
βš™οΈ Analysis settingsAuto-popup on load β€” language, model, VAD sensitivity
πŸ“Š Real-time progressSeparate progress for analysis & model download
β›” Cancel analysisStop anytime with cancel button
βœ‚οΈ Word-level editingDelete/restore words, split/merge clips
πŸ” Find & ReplaceCmd+F to batch-edit subtitle text
πŸ”„ Import FCPXMLRe-transcribe edited FCPXML with language & model selection
🌐 App LanguageSwitch UI language in Settings (Korean/English/Japanese/Chinese)
πŸ“€ ExportFCPXML (with inline iTT captions), SRT, iTT β€” all word-boundary split

Analysis Settings

CategorySettingDefaultDescription
SpeechLanguageKoreanKorean / English / Japanese / Chinese
ASR Model0.6B0.6B (fast) / 1.7B (accurate)
SilenceVAD Sensitivity0.500.1–0.9 (lower = more sensitive)
Min Silence200msShorter silences are ignored
Padding100msBuffer around speech segments
SubtitleMax Clip Length8s3–20s slider
Max Chars/Line20Subtitle line break threshold
Font Size42ptFCPXML subtitle font

Settings are persisted via UserDefaults across app restarts.

Build & Run

./build-release.sh                # Build β†’ dist/SilenciApp.app
open dist/SilenciApp.app          # Launch

Install from DMG

  1. Download Silenci-vX.X.X-macOS.dmg from Releases
  2. Open DMG β†’ drag SilenciApp to Applications
  3. First launch: Right-click (or Control+click) the app β†’ Open β†’ click Open in the dialog

    macOS shows "unidentified developer" warning for open-source apps. This is a one-time step β€” after this, the app opens normally.

  4. The app auto-installs Python, ffmpeg, and AI models on first launch (~1-2 min)

First Launch β€” Auto Setup

First launch setup flow

On first launch, the app automatically creates a Python venv and installs dependencies (~45 seconds). ASR models are downloaded on first analysis with byte-level progress tracking.

ItemPathSize
🐍 Python venv~/Library/Application Support/Silenci/venv/~1.5 GB
πŸ€– ASR model cache~/.cache/huggingface/hub/~1-2 GB

Complete Uninstall

Option 1 β€” From the app:

Menu bar β†’ Silenci β†’ Python ν™˜κ²½ μ‚­μ œ

Option 2 β€” Manual:

rm -rf ~/Library/Application\ Support/Silenci/
rm -rf ~/.cache/huggingface/hub/models--mlx-community--Qwen3-*

⌨️ CLI Usage

python -m silence_cutter <command> [options]
silence-cutter <command> [options]      # after pip install -e .

cut β€” Silence removal + subtitles

silence-cutter cut input.mp4                        # basic
silence-cutter cut input.mp4 -o output.fcpxml       # custom output
silence-cutter cut input.mp4 -l English --itt       # English + iTT
πŸ“‹ All options
OptionDefaultDescription
-o, --output<input>.fcpxmlOutput path
-l, --languageKoreanSpeech language
--asr-modelQwen3-ASR-1.7B-8bitASR model
--aligner-modelQwen3-ForcedAligner-0.6B-8bitAlignment model
--vad-threshold0.5VAD sensitivity (0–1)
--min-speech-ms250Min speech duration (ms)
--min-silence-ms300Min silence duration (ms)
--speech-pad-ms100Speech padding (ms)
--font-size42Subtitle font size
--max-subtitle-chars20Max chars per subtitle line
--ittfalseAlso generate iTT subtitles

multi β€” Multi-video merge

silence-cutter multi video1.mp4 video2.mp4 -o merged.fcpxml --itt

script β€” Script extraction

silence-cutter script input.mp4 -t -o script.txt    # with timecodes

resub β€” Regenerate subtitles

silence-cutter resub edited.fcpxml -o final.fcpxml --itt

extract β€” Extract FCPXML subtitles

silence-cutter extract timeline.fcpxml -t -o script.txt

πŸ“¦ Output Formats

FormatExtensionUse CaseSubtitle Splitting
FCPXML.fcpxmlFinal Cut Pro (silence cuts + inline titles + iTT captions)βœ… Word-based
SRT.srtUniversal subtitles (YouTube, VLC, etc.)βœ… Word-based
iTT.ittiTunes Timed Text (FCP compatible)βœ… Word-based
TXT.txtPlain text script (optional timecodes)β€”

All subtitle formats use word-level timestamps for precise splitting. FCPXML exports include both title text overlays (lane 1) and iTT inline captions (lane 2) β€” FCP shows both automatically.

Import to Final Cut Pro

File β†’ Import β†’ XML... β†’ select the .fcpxml file

The silence-removed timeline with embedded subtitles loads automatically.

FCPXML and iTT subtitles imported in Final Cut Pro

FCPXML timeline + iTT subtitles in Final Cut Pro


πŸ“₯ Installation

Requirements

ItemRequirement
OSmacOS 14.0+ (Apple Silicon)
Disk~2-4 GB for Python venv + ASR models

Python, ffmpeg, Homebrew are all auto-installed on first launch if not present. No manual setup needed.

Download from Releases β†’ see Install from DMG above.

CLI (for scripting/automation)

brew install ffmpeg
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
silence-cutter cut input.mp4

Dependencies

PackagePurpose
mlx-audioQwen3-ASR / ForcedAligner (MLX backend)
silero-vadVoice Activity Detection
torchSilero VAD runtime
soundfileWAV I/O
numpy<2Numerical computation
soynlpKorean tokenization (ForcedAligner)

πŸ”§ Technical Details

AI Models β€” Deep Dive

Silenci uses three AI models from the Qwen3 family, all running locally on Apple Silicon via the MLX framework.

Qwen3-ASR (Speech-to-Text)

0.6B1.7B
Modelmlx-community/Qwen3-ASR-0.6B-8bitmlx-community/Qwen3-ASR-1.7B-8bit
Parameters600M1.7B
Quantization8-bit (MLX)8-bit (MLX)
Disk size~600 MB~1.7 GB
Use caseFast drafts, short videosProduction quality, long-form
LanguagesKorean, English, Japanese, Chinese, and 10+ more

Qwen3-ASR is an encoder-decoder transformer trained on large-scale multilingual speech data. The MLX 8-bit quantized versions run efficiently on Apple Silicon's Neural Engine and GPU, achieving near-real-time transcription without requiring a cloud API.

How it's used in Silenci:

  1. Audio is extracted from video via ffmpeg (16kHz mono WAV)
  2. VAD segments are chunked into ≀30s pieces
  3. Each chunk is fed to asr.generate(audio, language=...) β†’ returns transcribed text

Qwen3-ForcedAligner (Word Timestamps)

Modelmlx-community/Qwen3-ForcedAligner-0.6B-8bit
Parameters600M
Quantization8-bit (MLX)
Disk size~600 MB
PurposeAlign transcribed text to audio β†’ word-level {text, start, end}

ForcedAligner takes the ASR output text and the original audio, then aligns each word to its exact position in the audio stream. This is what enables word-boundary splitting β€” the core innovation of Silenci.

How it's used:

  1. ASR produces text for a chunk: "Through being someone mobile"
  2. ForcedAligner receives audio + text β†’ outputs:
    [{text: "Through", start: 0.12, end: 0.45},
     {text: "being",   start: 0.47, end: 0.71},
     {text: "someone", start: 0.73, end: 1.15},
     {text: "mobile",  start: 1.18, end: 1.52}]
    
  3. Segment splitting only happens at word end times (never mid-word)

Coverage validation: If ForcedAligner output covers <75% of the ASR text, the result is discarded and the segment falls back to chunk-level timing (safety net for edge cases).

Silero VAD (Voice Activity Detection)

ModelSilero VAD v5
FrameworkPyTorch
Size~2 MB
SpeedProcesses 1 hour of audio in ~3 seconds
PurposeDetect speech vs. silence boundaries

Silero VAD is a lightweight neural network that classifies audio frames as speech or non-speech. It outputs speech timestamps used to:

  • Remove silence (the primary feature)
  • Define ASR chunk boundaries (speech segments β†’ 30s chunks)
  • Calculate energy for optimal split points

Configurable parameters:

ParameterDefaultEffect
threshold0.50Speech detection sensitivity (0.1=sensitive, 0.9=strict)
min_speech_ms250Minimum speech duration to keep
min_silence_ms200Minimum silence to detect as gap
speech_pad_ms100Padding added around speech segments

Subtitle Splitting Algorithm

The subtitle splitting engine runs both in Python (server-side) and Swift (export-side) with identical logic:

Priority 1  Split at punctuation or sentence endings (min 6 chars accumulated)
            Korean endings: μš”, λ‹€, 까, μ£ , κ³ , μ„œ, λ©°, λ©΄, μŠ΅λ‹ˆλ‹€, ν•©λ‹ˆλ‹€ …
            Punctuation: . ! ? γ€‚οΌŒ

Priority 2  Force-split when exceeding max_subtitle_chars
            - Include next word if ≀3 chars (prevents Korean particle separation)
            - Hard limit at max_chars + 8 (prevents infinite accumulation)

Priority 3  Auto-correct overlapping timestamps after splitting

Korean-specific post-processing: merge_orphan_josa() handles cases where ForcedAligner separates Korean particles (쑰사) at segment boundaries:

Before:  "λ§›μ§‘" | "을 검색을..."     ← "을" orphaned from its noun
After:   "맛집을" | "검색을..."      ← particle merged back

Frame Rate Handling

FCPXML requires frame-exact timing. Silenci uses Python Fraction arithmetic to avoid floating-point drift:

fpsFCP CodeFrame DurationNotes
23.97623981001/24000sDrop-frame NTSC film
2424100/2400sCinema
2525100/2500sPAL
29.9729971001/30000sDrop-frame NTSC
3030100/3000sNon-drop NTSC
59.9459941001/60000sHigh frame rate
6060100/6000sGaming/action
120120100/12000siPhone slo-mo

All time calculations use Fraction(numerator, denominator) β†’ converted to FCPXML offset="N/Ds" format. This ensures sample-accurate alignment even for long timelines (>1 hour).

Model Download & Caching

ASR models are downloaded from Hugging Face Hub on first analysis:

~/.cache/huggingface/hub/
  models--mlx-community--Qwen3-ASR-0.6B-8bit/
  models--mlx-community--Qwen3-ASR-1.7B-8bit/
  models--mlx-community--Qwen3-ForcedAligner-0.6B-8bit/

Silenci monkey-patches huggingface_hub.snapshot_download's tqdm progress bars to capture byte-level download progress and forward it to the UI via JSON-RPC notifications. This provides accurate "1.2 GB / 1.7 GB" progress display during model downloads.


πŸ—‚οΈ Project Structure

Silenci/
β”œβ”€β”€ silence_cutter/                  # Python package
β”‚   β”œβ”€β”€ server.py                    # JSON-RPC server (2-pass ASR)
β”‚   β”œβ”€β”€ vad.py                       # Silero VAD + silence-based splitting
β”‚   β”œβ”€β”€ transcribe.py                # Qwen3-ASR + ForcedAligner + josa merge
β”‚   β”œβ”€β”€ fcpxml.py                    # FCPXML generation + subtitle splitting
β”‚   β”œβ”€β”€ srt.py / itt.py              # SRT, iTT subtitles
β”‚   β”œβ”€β”€ pipeline.py                  # CLI pipeline
β”‚   └── ...
β”œβ”€β”€ SilenciApp/                # Swift macOS app
β”‚   β”œβ”€β”€ Package.swift
β”‚   └── Sources/
β”‚       β”œβ”€β”€ App.swift                # Entry point + menu (env cleanup)
β”‚       β”œβ”€β”€ ContentView.swift        # Main layout + analysis popup
β”‚       β”œβ”€β”€ Models/
β”‚       β”‚   β”œβ”€β”€ AnalysisService.swift    # Analysis runner + Python bridge
β”‚       β”‚   β”œβ”€β”€ AnalysisSettings.swift   # Settings model (UserDefaults)
β”‚       β”‚   └── ...
β”‚       β”œβ”€β”€ Services/
β”‚       β”‚   β”œβ”€β”€ PythonBridge.swift        # JSON-RPC communication
β”‚       β”‚   β”œβ”€β”€ PythonEnvironment.swift   # Auto venv install/cleanup
β”‚       β”‚   └── ExportService.swift       # FCPXML/SRT/iTT (word-based split)
β”‚       └── Views/
β”‚           β”œβ”€β”€ AnalyzeDialogView.swift   # Pre-analysis settings popup
β”‚           β”œβ”€β”€ AnalysisProgressView.swift # Progress + model download + cancel
β”‚           β”œβ”€β”€ ClipCardView.swift        # Clip card (video edit + subtitle)
β”‚           β”œβ”€β”€ WordFlowView.swift        # Word-level editing UI
β”‚           β”œβ”€β”€ RetranscribeSheetView.swift # FCPXML retranscribe settings + progress
β”‚           └── SettingsView.swift        # Settings sheet (incl. app language)
β”œβ”€β”€ build-release.sh                 # Release build β†’ dist/SilenciApp.app
β”œβ”€β”€ setup_mac.sh                     # Auto Python environment setup
└── docs/                            # Diagrams & screenshots

πŸ› οΈ Troubleshooting

ffmpeg/ffprobe not found
brew install ffmpeg

The app automatically adds /opt/homebrew/bin to PATH.

Model download is slow

ASR models are downloaded from Hugging Face on first analysis. Byte-level progress is shown in the app. After download, models are cached in ~/.cache/huggingface/hub/.

VAD is too sensitive / not sensitive enough

App: Adjust VAD Sensitivity slider in the analysis popup.

CLI:

DirectionParameter
More sensitive (catch quiet speech)--vad-threshold 0.3
Less sensitive (only clear speech)--vad-threshold 0.7
Remove short silences too--min-silence-ms 150
Only remove long silences--min-silence-ms 500
Subtitles are too short / too long

App: Adjust Max Chars in the analysis popup (default: 20).

CLI: --max-subtitle-chars 30 for longer lines.

Words are cut in the middle of subtitles

The 2-Pass ASR approach prevents mid-word cuts. If it still happens, try increasing --max-segment-seconds (default 8s β†’ 15s).


πŸ§‘β€πŸ’» Contributing

pip install -e ".[dev]"          # Install dev dependencies
pytest                           # Run tests
black --line-length 100 .        # Format
ruff check silence_cutter/       # Lint

Contributions are welcome! Please feel free to submit issues and pull requests.


⭐ Support

If you find this project useful, please consider giving it a star ⭐

It helps others discover the project and motivates continued development.

Star History Chart


πŸ“„ License

Apache License 2.0