AnimateIt

Videos as code. Declarative, frame-driven video compositions for Rails, rendered with headless Chromium (Playwright) + FFmpeg.

CI Gem Version Ruby >= 3.4


AnimateIt lets you describe a video as a Ruby class and a HAML template, preview it frame-by-frame in a bundled Studio UI, and render it to MP4/WebM/MOV/GIF. Each frame is a real web page — so you get the full power of CSS, your app's own components, and real data — captured by a headless browser and stitched with FFmpeg.

Requirements

  • Ruby >= 3.4, Rails >= 8.1.3
  • FFmpeg on the PATH (rendering)
  • Node + Playwright with a Chromium build (rendering): npx playwright install chromium
  • HAML (composition sidecar templates + Studio views)

Playwright and FFmpeg are only needed when you actually render; the gem loads Playwright lazily so production boot / asset precompile never touch it.

Installation

# Gemfile
gem "animate_it"
bundle install
bin/rails generate animate_it:install

The generator adds config/initializers/animate_it.rb and mounts the Studio in config/routes.rb (development/test only):

mount AnimateIt::Engine, at: AnimateIt.config.mount_path if Rails.env.local?

Visit http://localhost:3000/animate_it to open the Studio.

Writing a composition

Compositions live in app/videos/. They auto-register (via the id "..." DSL) and reload in development.

# app/videos/hello_video.rb
class HelloVideo < AnimateIt::Composition
  id "hello"
  fps 30
  size 1080, 1080
  duration 3.seconds

  assets_dir "app/assets/images/videos"
  output_basename "hello"

  outputs do
    mp4
    gif
    png frame: 45
  end

  beat :intro, at: 0, length: 45

  class Scene < AnimateIt::Scene
    def body
      expose(vars: animation_vars(title_opacity: at_act(:intro, [0, 30], [0, 1])))
      tag.div(class: "hero-render-canvas") { render_scene_template("canvas") }
    end
  end

  scene Scene
end
-# app/videos/hello_video/canvas.html.haml
:css
  .title { opacity: var(--title-opacity, 1); font-size: 6rem; font-weight: 800; }
.stage{style: @vars}
  .title Hello 👋

Animation helpers (at_global, at_act, beat_frame, beat_range), a per-frame CSS-variable bag (animation_vars), named beats, and audio / audio_loop tracks are all available. Compositions can render your app's real partials and ViewComponents — see AnimateIt.config.render_stylesheets below.

Rendering

The renderer needs your Rails server running (it drives a real browser against the Studio's filmstrip endpoint).

# start the app
bin/rails server

# in another shell — render a composition's declared outputs
bin/rails 'animate_it:render[hello]'
bin/rails 'animate_it:render[hello,0..45]'   # just a frame range

# or the packaged CLI (writes to tmp/animate_it/ by default)
bundle exec render_animate_it_video hello
bundle exec render_animate_it_video hello tmp/hello.mp4

You can also render from the Studio UI: open a composition, scrub the timeline, and click Render Video to watch progress live.

Host resolution

The renderer points the browser at ANIMATE_IT_HOST (falling back to the legacy RAILS_MOTION_HOST, then RAILS_HOST, then http://127.0.0.1:3000):

ANIMATE_IT_HOST=http://127.0.0.1:3001 bundle exec render_animate_it_video hello

Configuration

# config/initializers/animate_it.rb
AnimateIt.configure do |config|
  # Where the Studio mounts (development/test only). Default "/animate_it".
  config.mount_path = "/animate_it"

  # Host stylesheets to inject into every rendered frame. Only needed when your
  # compositions re-use host partials/components that expect their CSS. Names
  # are passed to `stylesheet_link_tag`. Default [].
  config.render_stylesheets = %w[application components/star-ratings]
end

Render concurrency for the Studio's background renderer is tunable via ANIMATE_IT_RENDER_CONCURRENCY and ANIMATE_IT_RENDER_STARTS_PER_SECOND.

Development

bin/setup                # bundle install
bundle exec rspec        # unit + request specs (uses spec/dummy)
bundle exec rubocop

# full render smoke test (needs ffmpeg + Playwright chromium)
RUN_RENDER_SMOKE=1 bundle exec rspec spec/rendering_spec.rb

Specs run against a minimal host app in spec/dummy — no external services, no database.

License

MIT.