Create polished videos fast — free, open, and yours.
Drift is a free, open-source desktop video editor from CutWire Studios. It brings the speed and simplicity of modern creator tools to your computer: drop in clips, add effects and stickers, generate captions, and export — with no subscription, no watermark, and no account required.
Built with Qt 6, QML, and FFmpeg. Preview and export share one compositor, so what you see is what you get.
Linux — install from Flathub:
flatpak install flathub org.cutwire.Drift
flatpak run org.cutwire.DriftOr grab a build for your platform from the latest release:
| Platform | Package |
|---|---|
| Linux | Flathub · AppImage |
| Windows | Installer (.exe) |
| macOS | Disk image (.dmg, Apple Silicon) · Build from source |
See all releases for previous versions and full changelogs.
Media bin, preview, clip inspector, and timeline
Multi-track timeline — overlays, titles, and video with filmstrip thumbnails
- Multi-track timeline — trim, split, snap, ripple, mute/hide tracks, and full undo/redo
- Effects & transitions — GPU effects, stylish transitions, and reusable look templates
- Stickers, emoji, titles & shapes — finish the look without leaving the editor
- Auto captions — speech-to-text captions you can edit on the timeline
- Cutouts & masks — isolate subjects, mask clips, and key out green screens
- Speed & motion — speed changes, reverse, fades, and animate to the beat of your music
- Audio tools — mixing, effect chain, and background noise cleanup
- Addons — optional fonts, stickers, effects, and speech models on demand
- Project bundles — package a project with its media for easy sharing and backup
- Export — MP4 (H.264 + AAC) that matches the preview, with quality presets
| Dependency | Version |
|---|---|
| CMake | ≥ 3.21 |
| C++ compiler | C++20 |
| Qt | 6.5+ (Quick, QuickControls2, Multimedia, Test, Concurrent, Widgets, OpenGL, Network, LinguistTools) |
| FFmpeg | 8.x (libavformat, libavcodec, libavutil, libswscale, libswresample, libavfilter) |
| libzstd | any (addon package decompression) |
| OpenSSL | 3.x, libcrypto only (addon signature verification) |
| SoundTouch | any (pitch shifting behind the voice effects) |
ONNX Runtime powers auto-subtitles (and related ML features). Drift does not link it — only its headers are needed to build, and the library itself is an addon the user installs from the Acceleration category, which is what makes the CPU / CUDA / WebGPU choice theirs rather than the packager's. The headers are downloaded automatically at configure time; pass -DDRIFT_FETCH_ONNXRUNTIME=OFF to use a system install instead. A development build also stages a CPU runtime into <build>/onnxruntime so it works before anything is installed — -DDRIFT_BUNDLE_ONNXRUNTIME=OFF (what the Flatpak manifests use) turns that off, and DRIFT_ONNXRUNTIME_DIR points at an extracted release instead.
On Debian/Ubuntu install libzstd-dev, libssl-dev and libsoundtouch-dev; on Arch, zstd, openssl and soundtouch; on macOS, brew install qt ffmpeg zstd openssl@3 sound-touch (see macOS). None of them has a download fallback — configure fails with a pkg-config error if the development headers are missing.
Optional: OpenCV for experimental background-removal builds (-DWITH_BGREMOVAL=ON). Only core, imgproc, and imgcodecs are linked.
Nothing has to be placed by hand. Fonts, emoji stickers, and speech models are addons (see below), so a clone builds and runs with no bundled assets.
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j$(nproc)Optional OpenCV background removal:
cmake -B build -DCMAKE_BUILD_TYPE=Debug -DWITH_BGREMOVAL=ON
cmake --build build -j$(nproc)./build/driftcd build
ctest --output-on-failureTest targets: Core, EditorState, Playback, Engine, MediaProbe, AddonPackage, Translations.
On a headless machine — CI, a container, a build server with no GPU — run it under Xvfb instead:
QT_QPA_PLATFORM=xcb xvfb-run -a -s "-screen 0 1280x1024x24" ctest --output-on-failureQT_QPA_PLATFORM=offscreen will not work: that plugin cannot create an OpenGL context without a
/dev/dri device, and the compositor tests then compare against a null frame. Software rendering
is not the issue — llvmpipe passes the whole suite once there is an X server.
AddonPackage verifies against a signed fixture in tests/data/, so that file has to be checked out with the repo.
UI strings use Qt Linguist. QML already wraps copy in qsTr(); C++ uses tr() / QCoreApplication::translate(). Pick a language in Settings, or leave it on System default to follow the OS locale.
Catalogs live in i18n/:
i18n/drift.ts— English source template, regenerated bylupdatei18n/drift_<lang>.ts— one file per language
After adding or changing user-visible strings:
cmake --build build --target update_translationsCommit the updated .ts files. .qm binaries are compiled into the app and are not committed.
MCP tool names, JSON schemas, agent errors, and copied mcp.json / agent-guide text stay English on purpose (src/mcp/ is excluded from lupdate). The Settings “Agent access” labels are ordinary UI and are translated.
Everything above applies, with Homebrew supplying the dependencies:
brew install cmake ninja qt ffmpeg zstd openssl@3 sound-touch
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_PREFIX_PATH="$(brew --prefix qt);$(brew --prefix openssl@3);$(brew --prefix)"
cmake --build build -j$(sysctl -n hw.ncpu)openssl@3 needs naming explicitly because Homebrew keeps it keg-only, and sysctl -n hw.ncpu stands in for nproc. Qt from qt.io works too — point CMAKE_PREFIX_PATH at .../6.x.x/macos instead.
The build produces an application bundle rather than a bare executable, so run it with:
open build/Drift.app # or: ./build/Drift.app/Contents/MacOS/Drift to see stderrThe bundle is not cosmetic: macOS treats a loose binary as a background process, with no Dock tile, no menu bar and no way to raise the window, and only Info.plist can set NSHighResolutionCapable, without which the UI and the preview render at 1x on Retina displays.
Effects, transitions, templates and audio effects are staged into Drift.app/Contents/Resources, which GpuPackageParse::defaultSearchPaths adds as a search root next to the directory holding the executable. The DRIFT_*_DIR overrides behave as they do elsewhere.
scripts/package-macos.shBuilds Release, runs macdeployqt to copy Qt, FFmpeg, OpenSSL, zstd and SoundTouch into Contents/Frameworks, drops the build machine's LC_RPATH entries, signs, and writes dist/Drift-<version>-<arch>.dmg. The rpath step matters: dyld searches the executable's rpaths before the @loader_path entries in the nested frameworks, so a bundle still listing /opt/homebrew/opt/qt6/lib loads the host's Qt on any Mac that has one.
Signing is ad-hoc by default — enough to launch locally, since Apple Silicon will not run unsigned binaries, but it still shows the unidentified-developer prompt elsewhere. Without notarisation, opening it needs right-click → Open, or xattr -dr com.apple.quarantine /Applications/Drift.app.
For a distributable build, sign with a Developer ID and notarise:
# either an App Store Connect API key…
export NOTARY_KEY=AuthKey_XXXXXXXXXX.p8 NOTARY_KEY_ID=XXXXXXXXXX NOTARY_ISSUER_ID=<uuid>
# …or an Apple ID with an app-specific password, which needs no API access
export NOTARY_APPLE_ID=you@example.com NOTARY_PASSWORD=abcd-efgh-ijkl-mnop NOTARY_TEAM_ID=TEAMID
scripts/package-macos.sh --identity "Developer ID Application: … (TEAMID)" --notarizeThat signs under the hardened runtime with resources/macos/Drift.entitlements, then notarises and staples the app and the image. Both entitlements are load-bearing: the hardened runtime otherwise blocks QtQml's JIT, and library validation stops Drift from dlopening the ONNX Runtime an Acceleration addon installs, which silently removes auto-subtitles, segmentation and face tracking.
The release workflow signs and notarises when these repository secrets exist, and falls back to an ad-hoc image when they do not — a fork builds unchanged.
| Secret | What it is |
|---|---|
MACOS_CERTIFICATE |
Developer ID Application certificate and key, exported as .p12, base64-encoded |
MACOS_CERTIFICATE_PWD |
Password set when exporting that .p12 |
MACOS_SIGN_IDENTITY |
Identity name, e.g. Developer ID Application: Your Name (TEAMID) |
| Then, for notarisation, either an App Store Connect API key: |
| Secret | What it is |
|---|---|
MACOS_NOTARY_KEY |
App Store Connect API key .p8, base64-encoded |
MACOS_NOTARY_KEY_ID |
That key's ID |
MACOS_NOTARY_ISSUER_ID |
Issuer UUID from App Store Connect |
or an Apple ID, which needs no App Store Connect API access:
| Secret | What it is |
|---|---|
MACOS_NOTARY_APPLE_ID |
Apple ID email of the developer account |
MACOS_NOTARY_PASSWORD |
App-specific password from appleid.apple.com |
MACOS_NOTARY_TEAM_ID |
Team ID, the parenthesised part of the signing identity |
Encode the two files with base64 -i cert.p12 | pbcopy. Setting only the certificate secrets signs without notarising. All of them require a paid Apple Developer account.
Homebrew ships single-architecture bottles, so the image is Apple Silicon only and Intel Macs build from source. Its deployment floor also follows Homebrew's rather than the macOS 12 a source build targets — build against Qt from qt.io to reach 12.
Fonts, emoji stickers, and speech models download at runtime rather than shipping in the binary. That keeps the install small and lets you take only what you need. Open the Addon Manager from the header (layers icon), or follow the install prompt in the font picker, stickers tab, or auto-subtitle panel.
Packages are .driftpkg archives — zstd-compressed, Ed25519-signed, and verified before install — under <AppDataLocation>/addons/. Format, registry, and installer live in src/engine/AddonPackage.*, src/engine/AddonRegistry.*, and src/models/AddonManager.*.
Effects and transitions are bundled and addons. They ship next to the binary so the editor works out of the box; effects.core / transitions.core addons can ship shader fixes without an app release. Content resolves highest-priority-first:
1. $DRIFT_*_DIR developer override
2. installed addon downloaded updates
3. <appDir>/<kind> bundled with the build
4. <AppDataLocation> hand-placed
Catalogs resolve duplicate ids first-root-wins, so an installed builtin.effects.gaussian_blur supersedes the bundled one. An addon cannot remove a bundled package — the bundled copy reappears when the addon no longer defines that id.
Opening a project that uses an effect or transition with no catalog entry reports it rather than silently dropping it from the render.
To work against local content instead of downloading:
DRIFT_EFFECTS_DIR=/path/to/effects \
DRIFT_TRANSITIONS_DIR=/path/to/transitions \
DRIFT_FONTS_DIR=/path/to/fonts \
DRIFT_STICKERS_DIR=/path/to/stickers \
DRIFT_WHISPER_MODEL_DIR=/path/to/whisper-small \
./build/driftBuilding and publishing addons lives in a separate repository, along with the Cloudflare Worker that serves them.
The endpoint and client token are defined in CMakeLists.txt and injected as compile definitions — src/models/AddonEndpoint.h only reads them.
cmake -B build -DDRIFT_ADDON_INDEX_URL=https://addons.example.com/v1/index \
-DDRIFT_ADDON_CLIENT_TOKEN=your-token
cmake -B build -DDRIFT_ADDON_INDEX_URL= # build with no addon service at allWith the service disabled the manager lists and installs nothing; already-installed, side-loaded, and DRIFT_*_DIR content still work.
The token is not a secret — it ships in every binary. It exists so the bucket cannot be crawled or hotlinked.
These are CMake cache variables: changing the default in CMakeLists.txt does not affect an existing build directory, so pass -D... again or reconfigure from scratch.
Optional, off at every launch. Settings → Agent access starts a localhost MCP server so Cursor or Claude Code can edit the open project (import media, place/trim clips, capture a still of the composition).
This is local process control of the editor, not a sandbox. Any process on the machine with the session token can use it. Bind is 127.0.0.1 only; the token rotates each time you enable it.
Cursor / Claude Code (this session): copy the snippet from Settings after enabling. The token changes every time.
One-time stdio setup (no token in mcp.json):
{
"mcpServers": {
"drift": {
"command": "/path/to/drift",
"args": ["--mcp-stdio"]
}
}
}drift --mcp-stdio attaches to a running editor. If Agent access is off, it exits with a one-line error.
Agents should call catalog, then toolbox, then apply with a list of ops. inspect({clips:true}) returns clip ids. capture returns a JPEG of the composition. See AGENTS.md for the full agent guide. The export toolbox encodes the timeline (settings + export({path})).
Flatpak: importing host files may fail unless you grant filesystem access:
flatpak override --filesystem=home org.cutwire.DriftNative and AppImage builds can import any path the process can read.
Built under build/tools/:
# Probe a media file
./build/tools/probe /path/to/video.mp4
# Render one composited frame from a saved project
./build/tools/renderframe project.dcut.json 1000000 out.pngArguments for renderframe: <project.json> <time_us> <output.png>.
src/
core/ Domain model (Project, Track, Clip, Keyframe, Effect) — no GUI
engine/ FFmpeg: ClipReader, FrameCompositor, AudioMixer, EffectProcessor, Exporter
models/ QML-facing models: AppController, AssetLibrary, TimelineModel, ClipListModel
mcp/ Opt-in localhost MCP server (agent access)
playback/ PlaybackEngine, PlaybackClock, CompositorService
preview/ PreviewItem (QQuickItem → QSGTexture)
qml/ UI panels and components
tests/ Unit tests (ctest) + tests/data (signed addon fixture)
tools/ Headless probe + renderframe
flatpak/ Flatpak / Flathub packaging
packaging/arch/ PKGBUILD for the Arch package
installer/windows/Inno Setup script for the Windows installer
scripts/ Release-notes extraction and asset sync helpers
cmake/ FindFFmpeg.cmake
Pushing a vX.Y.Z tag runs .github/workflows/release.yml, which
builds the AppImage, Windows installer, Arch package, and Flatpak bundle, then publishes them as a
GitHub release. Before tagging:
- Bump
project(Drift VERSION ...)inCMakeLists.txtandpkgverinpackaging/arch/PKGBUILD— the workflow refuses to publish if either disagrees with the tag. - Add a
<release version="X.Y.Z">entry toflatpak/org.cutwire.Drift.metainfo.xml. Its notes become the GitHub release body viascripts/extract_release_notes.py, so the software centre and the release page can never say different things. - Run the Build workflow manually (
workflow_dispatch) to prove each platform green — a tag is public the moment the release job finishes.
Flathub is submitted separately from flatpak/org.cutwire.Drift.flathub.yml; pin its commit: to
the tagged commit first.
| Target | Role |
|---|---|
driftcore |
Core domain + JSON persistence |
driftengine |
FFmpeg decode, compositing, effects, export |
drift |
Qt Quick application |
Unified frame server — Preview and export share FrameCompositor:
“Give me the composited RGBA frame + mixed audio at timeline time T (µs).”
Time model — Core timeline positions are int64_t microseconds (drift::TimeUs). QML uses seconds at the boundary via AppController.
Threading
| Thread | Responsibility |
|---|---|
| Main (GUI) | QML, models, undo stack, playhead UI |
| Decode workers | ClipReaderPool — one thread per active media path |
| Compositor | CompositorService — frames off the GUI thread |
| Audio (pull) | QAudioSink → PlaybackClock (audio-master) |
Data flow (video)
Media file → ClipReader → EffectProcessor
→ FrameCompositor (transforms, blending, text, masks)
→ PreviewItem (QSGTexture) | Exporter
Data flow (audio)
Media file → ClipReader → AudioMixer (volume, fades, audio effects)
→ QAudioSink | Exporter
Singletons registered in main.cpp:
EditorState/AppController— timeline controllerAssetLibrary— media bin
GPLv3 — see LICENSE.












