3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustで動画編集ソフトを作ろうとしたら、GUIライブラリ選定で深みに嵌った話

Last updated at Posted at 2025-12-09

TSG Advent Calendar 2025の10日目の記事です。

はじめに:なぜ車輪を再発明するのか

こんにちは、liesegangです。

突然ですが、皆さんは動画編集ソフト何を使っていますか? After EffectsはプロユースですがUIが複雑怪奇で動作も重い。AviUtlは拡張性が高く素晴らしいですが、設計が古く(32bitの壁とか, そうこうしていうちに64bit版が半年弱くらい前に出ましたが、それは一旦忘れておきます)、様々なユーザーによって魔改造されてまるで九龍城のように混沌として見えます。

私の趣味はモーショングラフィックス、特にvtuberなどのリリックビデオを作ることです。 「もっと直感的に、2Dの複雑なモーションを制御したい」「GPUをフル活用してヌルヌル動くプレビューが見たい」 そんな欲望が爆発した結果、「Rustで自分だけの最強の動画編集ソフトを作る」という沼に足を踏み入れることにしました。

image.png

本記事は、その開発初期にして最大の障壁、「GUIライブラリ選定」における死闘の記録です。

要件:ただのGUIではダメな理由

ToDoアプリを作るなら、適当なライブラリを選べば終わりです。しかし、動画編集ソフトには過酷な要件(または、私のこだわり)があります。

  1. リアルタイムGPUプレビュー:
    • 4K/60fpsの非圧縮バッファを遅延なく表示する必要があります。
    • つまり、GPUのメモリ空間(Vulkan/OpenGL Context)をGUIと動画レンダラーで共有できなければなりません。
  2. 日本語入力 (IME) 必須:
    • リリックビデオ(歌詞動画)を作るため、テキストボックスで日本語が打てないライブラリは論外です。
  3. モダンでCoolなUI:
    • モチベーション維持のため、見た目は重要です。

この要件を胸に、Rust界隈の主要GUIライブラリを片っ端から試しました。以下がその検証ログです。

候補1:CxxQt —— 巨人の肩に乗ろうとして押し潰された

最初は「業界標準」を狙いました。MayaやNukeなど、プロ用映像ツールの多くはQt (C++) で作られています
RustからQtを使える CxxQt なら、QMLでモダンなUIを書きつつ、バックエンドはRustで書けるはずだと考えました。

辛かったところ:バインディングの複雑怪奇さ

C++とRustの間のFFI(Foreign Function Interface)がとにかく辛い。
Qtのオブジェクトシステム(MOC)とRustの所有権モデルを調停するために、cxx クレートなどを駆使するのですが、ボイラープレートが膨大になります。

// cxxqtのブリッジ定義のイメージ
// UIのボタン一つ繋ぐだけで、C++の世界とRustの世界を行き来する呪文が必要
#[cxx_qt::bridge]
pub mod qobject {
  unsafe extern "C++" {
    include!("cxx-qt-lib/qimage.h");
    include!("cxx-qt-lib/qstring.h");
    include!("cxx-qt-lib/qlistitem.h");
    /// An alias to the QString type
    type QImage = cxx_qt_lib::QImage;
    type QString = cxx_qt_lib::QString;
    type QVector = cxx_qt_lib::QListItem;
  }

  unsafe extern "RustQt" {
    // The QObject definition
    // We tell CXX-Qt that we want a QObject class with the name MyObject
    // based on the Rust struct MyObjectRust.
    #[qobject]
    #[qml_element]
    #[qproperty(QString, name)]
    #[qproperty(u32, height)]
    #[namespace = "canvas_image"]
    type TrackInfo = super::TrackInfoRust;

    #[qobject]
    #[qml_element]
    #[qproperty(QString, name)]
    #[qproperty(f64, start)]
    #[qproperty(f64, duration)]
    #[qproperty(QString, color)]
    #[qproperty(u32, track)]
    #[namespace = "canvas_image"]
    type VideoClipInfo = super::VideoClipInfoRust;

    #[qobject]
    #[qml_element]
    #[qproperty(QImage, image)]
    #[qproperty(QVector<TrackInfo>, tracks)]
    #[qproperty(QVector<VideoClipInfo>, video_clips)]
    #[namespace = "canvas_image"]
    type CanvasImage = super::CanvasImageRust;
  }

  unsafe extern "RustQt" {
    // Declare the invokable methods we want to expose on the QObject
    #[qinvokable]
    #[cxx_name = "updateImage"]
    unsafe fn update_image(self: Pin<&mut CanvasImage>);

    #[qinvokable]
    #[cxx_name = "updateProject"]
    unsafe fn update_project(self: Pin<&mut CanvasImage>);
  }
}

use core::pin::Pin;
use cxx_qt_lib::{QImage, QImageFormat};

/// The Rust struct for the QObject
#[derive(Default)]
pub struct CanvasImageRust {
  image: QImage,
  tracks: Vec<TrackInfo>,
  video_clips: Vec<VideoClipInfo>,
}

#[derive(Serialize, Deserialize, Clone, Default)]
pub struct TrackInfoRust {
  pub name: String,
  pub height: u32,
}

#[derive(Serialize, Deserialize, Clone, Default)]
pub struct VideoClipInfoRust {
  pub start: f64,
  pub duration: f64,
  pub name: String,
  pub color: String,
  pub track: u32,
}
...

「QMLを書く時間」より「ビルドエラー(CMakeとCargoの戦争)を直す時間」の方が長くなり、「私は動画編集ソフトを作りたいのであって、バインディング職人になりたいわけではない」と思い、撤退を決めました。

候補2:Tauri —— Webの夢、帯域の現実

次に目をつけたのは、飛ぶ鳥を落とす勢いの Tauri です。
Webフロントエンド(React/Vueなど)の知識でUIが作れるのは魅力的。VSCodeのようなモダンなルックも余裕です。

辛かったところ:4K動画はブラウザには重すぎる

UIは最高です。しかし、「プレビュー画面」の実装で詰みました。

Rust側でレンダリングした画像データ(RGBA非圧縮)をWebViewに送る際、以下の壁にぶつかります。

  1. IPCのボトルネック: Base64等で送るとCPU負荷で死にます。
  2. メモリ制限: 動画制作において、メモリがあればあるほどキャッシュに動画を載せれて、リアルタイムプレビュー時間が長くなります。しかし、数十~百GB近い画像シーケンスデータをブラウザ(V8)のヒープに置こうとすればクラッシュします。数GB程度では全然足りないのです。
  3. オーバーレイの不具合: 「WebViewに穴を開けて、後ろにあるネイティブウィンドウを見せる(Transparent Overlay)」という回避策も試しましたが、そもそもWebViewに透明な穴を開ける事自体が無理でした。
// 理想:Rustからポインタを渡して描画
// 現実:IPC経由でデータを流すしかなく、fpsが出ない
tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![send_frame]) // <- ここが遅い
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

候補3:Iced / Slint —— 日本語入力の壁

Rust製(Native)のGUIライブラリ、IcedSlint も試しました。
これらはRustの所有権モデルに馴染むように設計されており、開発体験は良さそうでした。

辛かったところ:IMEサポート

致命的だったのが 日本語入力 (IME) のサポート状況 です。
アルファベット圏の開発者が多いためか、変換候補ウィンドウの位置がおかしかったり、入力中の未確定文字が表示されなかったりと、リリックビデオ制作には耐えられない挙動でした。
(※現在進行形で改善は進んでいるようですが、まだ実用段階ではありませんでした)

候補4:egui —— 最有力候補、しかしコンテキスト戦争へ

最終的にたどり着いたのが、即時モード(Immediate Mode)GUIの egui です。
ゲームエンジンのように毎フレーム描画する設計は、動画プレビューとの相性が抜群。日本語入力も winit 経由ながら、泥臭い修正がされており、そこそこ動きます。

辛かったところ:GPUコンテキストの競合

これで決まりかと思いきや、ラスボスが現れました。
「eguiが使うGPUコンテキスト」と「自作動画レンダラー(Vulkan/Skia)が使うコンテキスト」の競合です。

別なコンテキストを作ってフレームバッファの共有しようとしても、なぜかegui側でcontextを初期化すると、rendering側スレッドのGPUコンテキストがエラーを起こします。

また、同じコンテキストを使いまわそうとしても、RenderingPassが競合してうまくいきませんでした。

// 概念コード:コンテキストの取り合い
impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
        // ここでeguiが描画コマンドを発行する
        
        // 私「頼む!動画のプレビューをここに描画させてくれ!」
        // WGPU「ダメです。RenderPassは現在使用中です」
        self.my_renderer.render_to_frame(frame); // -> Error!
    }
}

まとめ:結局どうするのか

結論として、現在は egui + skiaでGPU shared memoryを作ってフレームバッファを共有 のルートで更に模索しています。

GUIライブラリ選定は、「何を作るか」によって正解がまるで違うことがわかりました。

  • 普通のツールなら Tauri 一択だと思います。webの開発経験があるなら尚更です。
  • 既存資産があるなら CxxQt も選択肢になるでしょう。
  • IME非対応でいいなら Iced も良い選択肢です。Rustだけで掛けますから。
  • そして、データの双方向バインディングとか気にせずにimmediate modeが使いたいなら egui でしょうか。複雑なUIを作るのには向いていそうです。

リポジトリ

とりあえず、現状の動画作成ソフトの進捗を以下においておきます。また、気が向いたら開発します。

それでは、明日のTSG Advent Calendarもお楽しみに!

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?