Video stabilization looks like a visual editing task.

With Gyroflow, much of it can also become a repeatable code pipeline.

The application can read motion data embedded by GoPro, Sony, Insta360, DJI, Blackmagic, RED, and Canon cameras. It also accepts external data from Betaflight Blackbox, ArduPilot, phone sensors, and the open .gcsv format.

The interesting question is not only whether Gyroflow works from a desktop UI.

It is whether we can give it a video, motion data, lens profile, stabilization preset, codec settings, and destination entirely from code.

The short answer is mostly yes.

Gyroflow is a cross-platform, GPL-licensed video stabilizer built around recorded gyroscope data, lens correction, motion synchronization, rolling-shutter correction, adaptive zoom, and GPU-accelerated rendering.

Gyroflow GitHub Source Code Gyroflow Website Gyroflow Documentation Gyroflow CLI Documentation Gyroflow Releases License: GPL-3.0 with additional permissions

What is Gyroflow?

Gyroflow uses the camera’s motion over time to calculate how each video frame should be transformed.

That is different from asking an editor to infer every movement only from image features. If the camera recorded accurate IMU data, Gyroflow already knows how it rotated. It can combine that signal with a lens model, timing information, smoothing, horizon control, rolling-shutter correction, and adaptive cropping.

For FPV footage, an external Betaflight Blackbox log can provide the motion source. That connects naturally with the Betaflight Blackbox Explorer post, although the tools solve different problems: Blackbox Explorer helps inspect a flight log, while Gyroflow uses motion data to transform the resulting video.

Repository and Architecture

I cloned and inspected this revision without installing or running the application:

repo: https://github.com/gyroflow/gyroflow
branch: master
commit: d918ab3594e539f25a67a9f1d2b8e042798f61f7
application version: 1.6.3
license: GPL-3.0-or-later with additional permissions

The main layers are easy to identify:

Layer Implementation Role
User interface Qt 6 and QML Desktop and mobile editing workflow
CLI Rust argh parser plus Qt core event loop Headless batch control
Stabilization engine gyroflow-core Rust crate Telemetry, sync, lens, smoothing, transforms, GPU kernels
Media rendering Rust and FFmpeg Decode, process, encode, audio, metadata
Project data JSON .gyroflow files Presets, projects, settings, optional embedded motion data
Motion interchange GCSV and supported camera/log formats IMU samples and timestamps

The split between core and rendering is important.

gyroflow-core does not depend on Qt or FFmpeg. The complete application does, because somebody still needs to decode compressed video, feed frames into the stabilizer, encode the result, carry audio, and manage hardware codecs.

Can We Do Everything as Code?

For a normal repeated render workflow, yes:

  • select input videos or project files
  • attach an external gyro log
  • load a settings preset
  • set synchronization parameters
  • set codec, bitrate, GPU use, output size, and output path
  • render multiple videos in parallel
  • watch a folder for new footage
  • export Gyroflow projects
  • export parsed metadata or per-frame camera motion
  • export STMaps for another compositor
  • print progress to standard output

But there is no single official “Gyroflow API” covering every layer.

If you write… Recommended interface
Shell scripts Gyroflow CLI plus .gyroflow preset files
Python subprocess plus JSON generated with json.dumps()
Rust batch orchestration Start with the CLI; use gyroflow-core only when you need native frame-level integration
A custom sensor logger Generate GCSV, then pass it through --gyro-file
A web service Build a queue around the CLI; Gyroflow does not ship a REST API

There are no official Python bindings for the current Rust implementation.

The separate gyroflow-python repository is the legacy pre-1.0 application, not a Python SDK for modern Gyroflow. New Python automation should call the current executable.

Using the Gyroflow CLI

The CLI is built into the normal application binary. On Linux that is normally ./Gyroflow; on Windows it is Gyroflow.exe.

The simplest route is to prepare a .gyroflow project in the GUI, then render it headlessly:

./Gyroflow flight.gyroflow \
  --stdout-progress \
  --overwrite

This is a good automation boundary because the project already records the video reference, lens calibration, sync offsets, smoothing, crop, keyframes, and output settings.

Render a Video with a Preset and JSON

You can also provide the pieces separately:

./Gyroflow flight.mp4 \
  --preset fpv.gyroflow \
  --out-params '{"codec":"H.265/HEVC","bitrate":80,"use_gpu":true,"audio":true,"output_folder":"./renders","output_filename":"flight-stabilized.mp4"}' \
  --sync-params '{"search_size":5,"processing_resolution":720,"max_sync_points":5,"auto_sync_points":true}' \
  --stdout-progress \
  --overwrite

That JSON is parsed and merged into Gyroflow’s defaults.

The output object can also control dimensions, pixel format, codec options, audio codec, interpolation, keyframe distance, FFmpeg encoder options, metadata comments, track preservation, padding, and separate trim exports.

If motion data lives in a separate file:

./Gyroflow flight.mp4 \
  --gyro-file flight.bbl \
  --preset fpv.gyroflow \
  --stdout-progress

Gyroflow supports Betaflight .bbl and .bfl, ArduPilot logs, GCSV, and multiple vendor-specific formats.

Automate an Incoming Folder

The watcher turns Gyroflow into a small local media worker:

./Gyroflow --watch /srv/incoming-footage \
  --preset fpv.gyroflow \
  --out-params '{"output_folder":"../stabilized","codec":"H.265/HEVC","bitrate":80}' \
  --stdout-progress

The code waits until a new file has stopped changing before adding it to the queue. That reduces the chance of opening a video while another machine is still copying it.

This is local process automation, not self-hosting in the usual web-app sense. There is no dashboard server, database, login, or HTTP endpoint to expose.

Python Wrapper for Gyroflow

Python is useful for discovering files, pairing videos with logs, generating JSON, launching jobs, and validating the results.

from __future__ import annotations

import json
import subprocess
from pathlib import Path


def stabilize(
    gyroflow: Path,
    source: Path,
    destination: Path,
    preset: Path,
) -> Path:
    destination.parent.mkdir(parents=True, exist_ok=True)

    output = {
        "codec": "H.265/HEVC",
        "bitrate": 80,
        "use_gpu": True,
        "audio": True,
        "output_folder": str(destination.parent.resolve()),
        "output_filename": destination.name,
    }
    sync = {
        "search_size": 5,
        "processing_resolution": 720,
        "max_sync_points": 5,
        "auto_sync_points": True,
    }

    command = [
        str(gyroflow),
        str(source.resolve()),
        "--preset", str(preset.resolve()),
        "--out-params", json.dumps(output, separators=(",", ":")),
        "--sync-params", json.dumps(sync, separators=(",", ":")),
        "--stdout-progress",
        "--overwrite",
    ]

    completed = subprocess.run(
        command,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
    )
    log = completed.stdout

    if completed.returncode != 0:
        raise RuntimeError(f"Gyroflow exited with {completed.returncode}\n{log}")
    if "Rendering failed:" in log or not destination.is_file():
        raise RuntimeError(f"Expected output was not produced\n{log}")

    return destination

Passing a list instead of shell=True keeps file paths and JSON from being interpreted again by a shell.

The file and log checks are intentional. In the source revision I inspected, render failures are printed from queue callbacks, but the top-level CLI does not explicitly set a non-zero process exit code for each job failure. A robust pipeline should not treat return code as its only success signal.

JSON Projects Are the Real Automation Format

Gyroflow projects and presets use the .gyroflow extension, but their outer format is JSON.

A project can contain:

  • video path and video dimensions
  • lens calibration data
  • gyro source, orientation, filtering, and bias
  • synchronization offsets
  • smoothing method and parameters
  • horizon lock and rolling-shutter settings
  • adaptive zoom and lens correction
  • keyframes and trim ranges
  • rendering options
  • optionally compressed raw or processed motion data

The current source exports project format version 4.

Export mode determines how self-contained the file is:

# Simple project
./Gyroflow flight.mp4 --export-project 1

# Include gyro data
./Gyroflow flight.mp4 --export-project 2

# Include processed gyro data
./Gyroflow flight.mp4 --export-project 3

# Render video and save a project
./Gyroflow flight.mp4 --export-project 4

Modes with embedded motion data remain JSON files, but some large values are compressed as base91-encoded CBOR strings. They are designed for Gyroflow interchange, not pleasant manual editing.

Presets are also JSON .gyroflow files. The main difference is that they do not contain a videofile, so the CLI recognizes them as settings to apply.

You can pass a small preset inline:

./Gyroflow flight.mp4 \
  --preset '{"version":2,"stabilization":{"fov":1.2,"lens_correction_amount":1.0}}'

For serious work, I would create the preset in Gyroflow, inspect the resulting JSON, keep it in version control, and pin the Gyroflow release used by the automation.

Export Metadata Instead of Video

Gyroflow can be useful as a telemetry conversion tool even when you do not want a stabilized render.

Export parsed metadata:

./Gyroflow flight.mp4 --export-metadata 2:parsed.json

Export selected original and stabilized motion values:

./Gyroflow flight.mp4 \
  --export-metadata 3:camera-motion.json \
  --export-metadata-fields '{"original":{"gyroscope":true,"accelerometer":true,"quaternion":true,"euler_angles":true},"stabilized":{"quaternion":true,"euler_angles":true},"zooming":{"minimal_fovs":true,"fovs":true,"focal_length":true}}'

The output extension for type 3 selects CSV, JSON, USD, or JSX. That opens useful code workflows for data analysis, Blender/USD pipelines, and After Effects camera data.

Gyroflow can also generate EXR STMaps:

./Gyroflow flight.gyroflow --export-stmap 1:./stmaps

Mode 1 writes a single map pair; mode 2 generates per-frame maps.

What About Rust?

The current engine is Rust, and gyroflow-core is a real reusable crate inside the repository.

A Rust project can pin it directly:

[dependencies]
gyroflow-core = { git = "https://github.com/gyroflow/gyroflow.git", rev = "d918ab3594e539f25a67a9f1d2b8e042798f61f7", package = "gyroflow-core" }

That gives native access to telemetry parsing, IMU integration, lens models, synchronization, smoothing, transforms, zooming, keyframes, STMaps, and GPU processing.

It does not immediately give you this:

gyroflow::stabilize_video("in.mp4", "out.mp4")?;

The full video path still needs probing, decoding, pixel formats, timestamps, frame transforms, encoding, audio, and metadata. Gyroflow implements those pieces in its application rendering modules, while its RenderQueue is tied to Qt.

That is why I would use the CLI from Rust too unless the project genuinely needs frame-level integration. The separate OpenFX plugin proves that core integration is possible, but it is application-development work, not a tiny SDK call.

Installation Options, Without Installing It Here

For normal use, upstream offers Windows and Apple store packages, Android and iOS apps, macOS Homebrew, and downloadable archives for Windows, macOS, and Linux.

Linux users can download the release archive and invoke its Gyroflow binary from a terminal. Developers can build from source with Rust and just after installing the platform dependencies documented upstream.

I did not install packages, download a release binary, build the source, or run Gyroflow for this review. The conclusions here come from the official documentation and direct inspection of the cloned Rust/QML repository.

The Most Reliable Code-First Workflow

I would structure a production pipeline like this:

camera video + embedded IMU, or video + external GCSV/Blackbox log
  -> version-controlled .gyroflow preset/project
  -> Python/Rust/shell job runner
  -> official Gyroflow CLI
  -> progress log + explicit output validation
  -> stabilized video, project, metadata, or STMaps

The GUI becomes a visual configuration editor, not a mandatory step for every clip.

Tune a representative clip once. Export the settings. Review the JSON. Pin a release. Then run new footage through the same configuration as code.

Conclusion

Gyroflow is more automatable than its polished desktop interface first suggests.

The official CLI covers rendering, batch inputs, watch folders, external motion logs, JSON overrides, presets, projects, metadata, STMaps, GPU selection, and progress output. Python can orchestrate that cleanly without a dedicated binding.

Rust offers a deeper route through gyroflow-core, but the full media pipeline is not packaged as a simple headless SDK. Use it when you need custom frame processing or a plugin; use the CLI when you need dependable application-level rendering.

So, can we do everything as code?

We can automate almost everything in a repeatable render pipeline. The remaining boundary is authoring and validating good stabilization choices—and the lack of one stable public API for every control exposed by the GUI.

FAQ