NexClip AINexClip AI

即時ダウンロード。Apple Silicon のみ対応。

macOS 14.6(Sonoma)以降が必要です。

エンジニアリング2026 年 3 月 2 日

Rust + UniFFI で macOS 動画エディターを作る: ソロファウンダーのアーキテクチャ

私は NexClip AI—— macOS 向け、長尺クリエイター用の AI 動画エディターを作っています。ソロファウンダーとして、コアエンジンは一度書いて複数プラットフォームで共有する必要がありました。それを可能にしたのが Rust + UniFFI です。

Rust + UniFFI で macOS 動画エディターを構築するアーキテクチャ図

課題

動画編集者には大きなボトルネックがあります —— 長尺の素材からハイライトクリップを特定すること。45 分の講義動画なら、手作業で丸 1 日かかることもあります —— 観て、トピックを特定し、トランスクリプトを読み、タイムスタンプをマッピングし、クリーンなクリップを作る。

NexClip AI はこれをパイプラインで自動化します:

  1. 文字起こし —— 単語レベルのタイムスタンプ付き音声認識
  2. NLP 処理 —— 日本語 / 英語の文・文節分割
  3. タイムコード補正 —— 精度向上のためのセグメント境界の調整
  4. クリッププランニング —— 選択されたトピックからクリップを生成

ステップ 2〜4 はコンピュート負荷が高く、オンデバイス実行が必要でした。これを効率よく処理でき、かつどこでも動く言語が必要でした。

なぜ Rust か?

妥協のないパフォーマンス

タイムコード補正は音声 RMS データを解析し、無音区間を検出し、境界をリアルタイムで調整します。Rust はランタイムオーバーヘッドゼロでネイティブコードにコンパイルされます。

メモリ安全性の保証

単語レベルのタイミングデータは、精密な浮動小数境界を持つ数千の struct を扱います。Rust の所有権モデルは、C/C++ で私を悩ませたであろうバグを未然に防ぎます。

一度書いて、どこでも動かす

同じ Rust クレートが、macOS/iOS 用の静的ライブラリ(UniFFI 経由)、Web 用の WASM モジュール、Node.js 用のネイティブアドオン(Napi 経由)にコンパイルされます。1 つの実装、3 つのプラットフォーム。

クレートアーキテクチャ

crates/
├── kirinuki-core          # Shared types & error definitions
├── kirinuki-timecode      # Timecode correction engine
├── kirinuki-clip          # Clip planning & optimization
├── kirinuki-nlp           # Japanese NLP engine
├── kirinuki-nlp-english   # English NLP engine
├── kirinuki-ffi-swift     # Swift bindings (UniFFI)
└── kirinuki-ffi-node      # Node.js bindings (Napi)

各クレートは単一の責務を持ちます。FFI クレートはプラットフォーム型とコア型の変換を行う薄いラッパー —— ビジネスロジックは FFI レイヤーには置きません。

コア型: 基盤

pub struct Segment {
    pub start: f64,      // seconds
    pub end: f64,
    pub text: String,
    pub confidence: Option<f64>,
    pub words: Vec<Word>,
}

pub struct Word {
    pub start: f64,
    pub end: f64,
    pub word: String,
    pub confidence: Option<f64>,
}

これらの型がパイプライン全体を流れます —— 文字起こしから最終的なクリッププランまで。シンプルでフラットで、FFI 境界を越えたシリアライズも簡単です。

タイムコード補正: 精度が物を言う領域

音声認識のタイムスタンプは近似値です。2.34 秒と報告された単語が、実際には 2.31 秒から始まっているかもしれない。数百単語も重なればエラーが累積します。

タイムコード補正エンジンはセグメントを複数の精緻化ステージに通します —— 音声特性を解析し、境界を自然な区切り点に揃え、衝突を解決し —— ミリ秒レベルの精度を達成します。

pub fn adjust_all_segments(
    segments: &[Segment],
    options: &AdjustOptions,
) -> AdjustmentResult

結果:セグメント境界は数ミリ秒の精度。

日本語 NLP: GiNZA から Vibrato へ

当初は Python ベースの NLP ライブラリ(GiNZA)を Docker コンテナで動かしていました。問題は明らかでした:

  • コールドスタート:約 10 秒
  • Python ランタイムが必要
  • ネットワーク往復のオーバーヘッド
  • オンデバイスで動かせない

Vibrato—— ピュア Rust の形態素解析器 —— への移行で、Docker・Python・ネットワーク依存を完全に排除しました。日本語の文分割、文節検出、品詞タグ付けはすべてアプリ内でネイティブに動きます。

UniFFI: Swift へのブリッジ

UniFFI: Rust から Swift へのブリッジアーキテクチャ図。Rust クレートが UniFFI のコード生成を通じて Swift Actor にマッピングされる様子を示す

UniFFI(Mozilla 製)は Rust コードから Swift バインディングを自動生成します。C ヘッダーを手書きする必要はありません。

#[derive(Debug, Clone, uniffi::Record)]
pub struct SwiftWord {
    pub start: f64,
    pub end: f64,
    pub word: String,
    pub confidence: Option<f64>,
}

#[uniffi::export]
pub fn correct_timecodes(
    segments: Vec<SwiftSegment>,
    options: Option<SwiftAdjustOptions>,
) -> SwiftAdjustmentResult {
    let core_segments = segments.into_iter()
        .map(Into::into).collect();
    let core_options = options
        .map(Into::into).unwrap_or_default();
    let result = kirinuki_timecode::adjust_all_segments(
        &core_segments, &core_options,
    );
    result.into()
}

UniFFI はネイティブな Swift 型を含む .swift ファイル、FFI 境界用の C ヘッダー、そして Xcode 用のモジュールマップを生成します。生成された Swift コードは呼び出し側から見て完全にネイティブ —— UnsafePointer は一切見えません。

ビルドパイプライン

# 1. Compile Rust → static library
cargo build --release -p kirinuki-ffi-swift \
  --target aarch64-apple-darwin

# 2. Generate Swift bindings
cargo run -p kirinuki-ffi-swift --bin uniffi-bindgen \
  -- generate \
  --library target/.../libkirinuki_ffi_swift.dylib \
  --language swift --out-dir Sources

# 3. Package as XCFramework
xcodebuild -create-xcframework \
  -library target/.../libkirinuki_ffi_swift.a \
  -headers Headers \
  -output KirinukiCore.xcframework

XCFramework は SPM のバイナリターゲットとして消費されます:

let package = Package(
    name: "KirinukiCore",
    platforms: [.macOS(.v13), .iOS(.v16)],
    targets: [
        .binaryTarget(
            name: "KirinukiCoreFFI",
            path: "KirinukiCore.xcframework"
        ),
        .target(
            name: "KirinukiCore",
            dependencies: ["KirinukiCoreFFI"]
        ),
    ]
)

Swift 側: Actor ベースのサービス

各 Rust モジュールは Swift Actor にマッピングされます:

actor TimecodeService {
    func correctTimecodes(
        _ segments: [Segment],
        options: SwiftAdjustOptions? = nil
    ) async -> CorrectionResult {
        await Task.detached(priority: .userInitiated) {
            let swiftSegments = segments
                .map { $0.toSwiftSegment() }
            let result = KirinukiCore.correctTimecodes(
                segments: swiftSegments,
                options: options
            )
            return CorrectionResult(from: result)
        }.value
    }
}

Actor がスレッドセーフティを提供します。Task.detached は重い計算をメインスレッドから外し、UI の応答性を保ちます。

得られた教訓

UniFFI の proc macros > UDL ファイル

proc macros なら型定義が Rust コード内に留まります —— 別ファイルのスキーマを管理する必要がありません。

FFI 型はフラットに保つ

ネストした enum や複雑なジェネリクスは FFI 境界をうまく越えません。nullable フィールドにはシンプルな struct と Option&lt;T&gt; を使いましょう。

重いリソースにはシングルトンパターン

NLP 辞書は大きい。起動時に 1 度ロードし、セッション中は使い回しましょう。

双方向変換は避けられない

すべての型にコア型への変換用の From&lt;SwiftType&gt; とその逆が必要になります。ボイラープレートですが、コアロジックをクリーンかつプラットフォーム非依存に保てます。

Rust レイヤー単体でテストする

Rust のユニットテストはミリ秒で走ります。アルゴリズムの正しさを確かめるのに Xcode のビルドを待つ必要はありません。

結果

7

Rust クレート

70+

エクスポート関数

0

unsafe Swift コード

1

ビルドスクリプト

ソロファウンダーとして、Rust + UniFFI はパフォーマンスクリティカルなコードを一度書いてどこにでも出荷することを可能にしてくれます。初期セットアップのコストは確かにありますが、見返りは絶大です。本格的な計算を必要とするネイティブアプリにとって、Rust + UniFFI は驚くほど痛みの少ないブリッジを提供してくれます。

よくある質問

コアエンジンに Swift ではなく Rust を使う理由は?

Rust はメモリ安全性、ゼロコスト抽象化、クロスプラットフォームのコンパイルを提供します。同じクレートが macOS/iOS、Web(WASM)、Node.js にコンパイルされます。コアを Rust で一度書くことで、プラットフォームごとに別実装を保守する手間が省けます。

UniFFI とは何か、Swift とどう連携するのか?

UniFFI は Rust コードから Swift バインディングを自動生成する Mozilla プロジェクトです。Rust の struct や関数に UniFFI の proc macros を注釈すると、ネイティブな Swift 型、C ヘッダー、Xcode 用のモジュールマップが生成されます。手動のブリッジは不要です。

NexClip AI は Rust クレートを何個使っている?

7 つです:kirinuki-core(共通型)、kirinuki-timecode(タイムコード補正)、kirinuki-clip(クリッププランニング)、kirinuki-nlp(日本語 NLP)、kirinuki-nlp-english(英語 NLP)、kirinuki-ffi-swift(Swift バインディング)、kirinuki-ffi-node(Node.js バインディング)。合わせて UniFFI 経由で 70+ の関数をエクスポートしています。

NexClip AI を試す

macOS 版、無料。動画を読み込んで、AI が抽出したすべてのトピックを見て、重要なものを選ぶ。

macOS 版を無料で試す

トピックベース編集とは?AI オートクリッピングと比較する

NexClip AI

NexClip AI

Topic-Based Editing: Pick your topics. Get your clips.

Check it out on Product Hunt →

Related Articles