animate_it 0.1.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -1
- data/README.md +66 -9
- data/app/controllers/animate_it/application_controller.rb +2 -1
- data/app/controllers/animate_it/audio_controller.rb +15 -2
- data/app/views/animate_it/studio/_studio_script.html.haml +23 -4
- data/lib/animate_it/scene.rb +4 -2
- data/lib/animate_it/version.rb +1 -1
- metadata +30 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95bcfafc0a059c64d5f3a4097a308ba2b7b9fff58cafdb18de720926e02cb07e
|
|
4
|
+
data.tar.gz: 268da7e42d644633ee56bafb7528e7051280474647873d31bad585b07bd472cb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d4cd9739b39aecc9d9cedf7d159718a768b7ba26ad3c4382e6f1bd96226ba70c4d59d708c57c263124eb07d39f0ff21bf3daab91733e774ffa15457dd8f3cf9e
|
|
7
|
+
data.tar.gz: c6f480ba02780a0c2cff836d061247961985138916607a318e6a4311456e8627684179dd52a81d16ab8fe30b413192f9126e9c3073306799299c8a8ff5ec06ec
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,44 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Fixed
|
|
10
|
+
- Studio audio is now served with HTTP byte-range support (Accept-Ranges,
|
|
11
|
+
206 Partial Content). Browsers refuse to seek media served without it, so
|
|
12
|
+
play-pause-play and scrub-then-play restarted clips from 0:00.
|
|
13
|
+
|
|
14
|
+
## [0.3.1] - 2026-07-22
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Relaxed the HAML dependency from `~> 6.3` to `>= 6.3` so hosts on HAML 7 are
|
|
18
|
+
not forced to downgrade.
|
|
19
|
+
|
|
20
|
+
## [0.3.0] - 2026-07-22
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Studio playback started from a scrubbed position could seek the audio before
|
|
24
|
+
its metadata had loaded; the seek silently failed and the clip played from
|
|
25
|
+
0:00 instead of the playhead offset. The seek is now deferred until
|
|
26
|
+
`loadedmetadata`.
|
|
27
|
+
- Studio playback now stops on Turbo navigation (`turbo:before-visit`,
|
|
28
|
+
`turbo:before-cache`) and `pagehide` — Turbo swaps the body without unloading
|
|
29
|
+
the window, so a detached, still-playing audio element kept sounding under
|
|
30
|
+
the next page.
|
|
31
|
+
|
|
32
|
+
## [0.2.0] - 2026-07-09
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- Scene sidecar templates can now be authored in ERB (`canvas.html.erb`) as well
|
|
36
|
+
as HAML — Rails resolves whichever exists, so HAML and ERB scenes can be mixed
|
|
37
|
+
in one app.
|
|
38
|
+
- Test matrix across Rails 7.2 and 8.1 on Ruby 3.3 and 3.4, via Appraisal.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- Lowered the minimum supported versions to Rails >= 7.2 and Ruby >= 3.3
|
|
42
|
+
(previously Rails >= 8.1.3, Ruby >= 3.4).
|
|
43
|
+
- HAML is now a runtime dependency, so the Studio UI and HAML scenes render on
|
|
44
|
+
any host without the host having to add HAML itself (fixes an ERB-only host
|
|
45
|
+
crashing when mounting the Studio).
|
|
46
|
+
|
|
9
47
|
## [0.1.0] - 2026-07-08
|
|
10
48
|
|
|
11
49
|
### Added
|
|
@@ -20,5 +58,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
20
58
|
- `render_animate_it_video` executable and `animate_it:render` rake task.
|
|
21
59
|
- `animate_it:install` generator.
|
|
22
60
|
|
|
23
|
-
[Unreleased]: https://github.com/
|
|
61
|
+
[Unreleased]: https://github.com/joinbuildit/animate_it/compare/v0.3.1...HEAD
|
|
62
|
+
[0.3.1]: https://github.com/joinbuildit/animate_it/compare/v0.3.0...v0.3.1
|
|
63
|
+
[0.3.0]: https://github.com/growth-constant/animate_it/compare/v0.2.0...v0.3.0
|
|
64
|
+
[0.2.0]: https://github.com/growth-constant/animate_it/compare/v0.1.0...v0.2.0
|
|
24
65
|
[0.1.0]: https://github.com/growth-constant/animate_it/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
|
|
4
|
+
<img src="assets/logo.svg" alt="AnimateIt" width="360">
|
|
5
|
+
</picture>
|
|
3
6
|
</p>
|
|
4
7
|
|
|
5
8
|
<p align="center">
|
|
6
|
-
<strong>
|
|
7
|
-
|
|
9
|
+
<strong>Remotion for Rails.</strong> Make videos from your app's own components and data,
|
|
10
|
+
written in Ruby — no React, no video editor, no After Effects.
|
|
8
11
|
</p>
|
|
9
12
|
|
|
10
13
|
<p align="center">
|
|
@@ -15,14 +18,23 @@
|
|
|
15
18
|
|
|
16
19
|
---
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
[Remotion](https://www.remotion.dev/) made it possible to build videos in React.
|
|
22
|
+
**AnimateIt brings the same idea to Ruby on Rails** — without React, a JavaScript
|
|
23
|
+
project, or a video editor.
|
|
24
|
+
|
|
25
|
+
You describe a video as a Ruby class and a HAML template, preview it frame-by-frame
|
|
26
|
+
in a bundled **Studio** UI, and render it to MP4/WebM/MOV/GIF. Every frame is a real
|
|
27
|
+
web page, so you get the full power of CSS and — crucially — **your app's own
|
|
28
|
+
components, styles, fonts, and real data**. A headless browser captures each frame
|
|
29
|
+
and FFmpeg stitches them into a video.
|
|
30
|
+
|
|
31
|
+
It's built for indie hackers and Rails developers who want to promote their projects
|
|
32
|
+
with polished product demos, launch clips, and social ads — reusing the UI they've
|
|
33
|
+
already built, staying in Ruby, and skipping the whole "learn a video editor" detour.
|
|
22
34
|
|
|
23
35
|
## Requirements
|
|
24
36
|
|
|
25
|
-
- Ruby >= 3.
|
|
37
|
+
- Ruby >= 3.3, Rails >= 7.2 (tested against Rails 7.2 and 8.1)
|
|
26
38
|
- **FFmpeg** on the PATH (rendering)
|
|
27
39
|
- **Node + Playwright** with a Chromium build (rendering): `npx playwright install chromium`
|
|
28
40
|
- **HAML** (composition sidecar templates + Studio views)
|
|
@@ -99,6 +111,23 @@ CSS-variable bag (`animation_vars`), named `beat`s, and `audio` / `audio_loop`
|
|
|
99
111
|
tracks are all available. Compositions can render your app's real partials and
|
|
100
112
|
ViewComponents — see `AnimateIt.config.render_stylesheets` below.
|
|
101
113
|
|
|
114
|
+
### HAML or ERB
|
|
115
|
+
|
|
116
|
+
Scene sidecar templates can be authored in **HAML** (`canvas.html.haml`) or
|
|
117
|
+
**ERB** (`canvas.html.erb`) — Rails resolves whichever file exists, so you can
|
|
118
|
+
mix engines across scenes. The HAML canvas above is equivalent to:
|
|
119
|
+
|
|
120
|
+
```erb
|
|
121
|
+
-# app/videos/hello_video/canvas.html.erb
|
|
122
|
+
<style>.title { opacity: var(--title-opacity, 1); font-size: 6rem; font-weight: 800; }</style>
|
|
123
|
+
<div class="stage" style="<%= @vars %>">
|
|
124
|
+
<div class="title">Hello 👋</div>
|
|
125
|
+
</div>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The gem bundles HAML for its own Studio UI, so you never need to add HAML to your
|
|
129
|
+
app to use it — ERB-only apps work out of the box.
|
|
130
|
+
|
|
102
131
|
## Rendering
|
|
103
132
|
|
|
104
133
|
The renderer needs your Rails **server running** (it drives a real browser against
|
|
@@ -147,6 +176,19 @@ end
|
|
|
147
176
|
Render concurrency for the Studio's background renderer is tunable via
|
|
148
177
|
`ANIMATE_IT_RENDER_CONCURRENCY` and `ANIMATE_IT_RENDER_STARTS_PER_SECOND`.
|
|
149
178
|
|
|
179
|
+
## Claude skill
|
|
180
|
+
|
|
181
|
+
If you use [Claude Code](https://claude.com/claude-code), this repo ships an
|
|
182
|
+
Agent Skill that teaches Claude how to author AnimateIt compositions — the DSL,
|
|
183
|
+
the render pipeline, embedding, rendering your app's real partials, motion
|
|
184
|
+
craft, and the gotchas. Install it into your project's `.claude/skills/`:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
npx animate-it-skills # copies the skill into ./.claude/skills
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The skill source lives in [`skills/`](skills/).
|
|
191
|
+
|
|
150
192
|
## Development
|
|
151
193
|
|
|
152
194
|
```bash
|
|
@@ -156,11 +198,26 @@ bundle exec rubocop
|
|
|
156
198
|
|
|
157
199
|
# full render smoke test (needs ffmpeg + Playwright chromium)
|
|
158
200
|
RUN_RENDER_SMOKE=1 bundle exec rspec spec/rendering_spec.rb
|
|
201
|
+
|
|
202
|
+
# run the suite against a specific Rails version (see Appraisals)
|
|
203
|
+
bundle exec appraisal install
|
|
204
|
+
bundle exec appraisal rails-7.2 rspec
|
|
205
|
+
bundle exec appraisal rails-8.1 rspec
|
|
159
206
|
```
|
|
160
207
|
|
|
161
208
|
Specs run against a minimal host app in `spec/dummy` — no external services, no
|
|
162
209
|
database.
|
|
163
210
|
|
|
211
|
+
## Credits
|
|
212
|
+
|
|
213
|
+
AnimateIt is inspired by [**Remotion**](https://www.remotion.dev) — the framework
|
|
214
|
+
that pioneered making videos programmatically in React. AnimateIt brings that
|
|
215
|
+
"videos as code" idea to the Ruby on Rails ecosystem, natively and without React.
|
|
216
|
+
Huge thanks to the Remotion team for the inspiration.
|
|
217
|
+
|
|
164
218
|
## License
|
|
165
219
|
|
|
166
|
-
[MIT](MIT-LICENSE)
|
|
220
|
+
AnimateIt is released under the [**MIT License**](MIT-LICENSE) — free and open source,
|
|
221
|
+
with **no restrictions on commercial or business use**. Individuals, startups,
|
|
222
|
+
agencies, and companies of any size can use it in open-source and closed-source
|
|
223
|
+
projects at no cost, forever. No paid tiers, no seat limits.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
module AnimateIt
|
|
2
2
|
class ApplicationController < ActionController::Base
|
|
3
|
-
# Composition sidecar templates live at app/videos/<composition>/*.html
|
|
3
|
+
# Composition sidecar templates live at app/videos/<composition>/*.html.*
|
|
4
4
|
# in the host app — outside the engine's view path, so add it explicitly.
|
|
5
|
+
# Sidecars may be HAML or ERB; Rails' lookup resolves whichever exists.
|
|
5
6
|
prepend_view_path Rails.root.join("app/videos")
|
|
6
7
|
|
|
7
8
|
# Sidecar templates routinely render host-app partials (e.g. the real
|
|
@@ -10,12 +10,25 @@ module AnimateIt
|
|
|
10
10
|
file_path = resolve_audio_path(segment.source[:path])
|
|
11
11
|
return head :not_found unless file_path && File.file?(file_path)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
disposition: "inline"
|
|
13
|
+
serve_with_byte_ranges(file_path)
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
private
|
|
18
17
|
|
|
18
|
+
# send_file ignores Range headers, and browsers refuse to seek media
|
|
19
|
+
# served without byte-range support — every currentTime seek snaps back
|
|
20
|
+
# to 0:00, so the Studio player restarts clips from the beginning.
|
|
21
|
+
# Rack::Files implements range semantics (Accept-Ranges, 206/416).
|
|
22
|
+
def serve_with_byte_ranges(file_path)
|
|
23
|
+
status, headers, body = Rack::Files.new(File.dirname(file_path)).serving(request, file_path)
|
|
24
|
+
headers["content-type"] = Mime::Type.lookup_by_extension(File.extname(file_path).delete(".")).to_s
|
|
25
|
+
headers["content-disposition"] = "inline"
|
|
26
|
+
headers["accept-ranges"] = "bytes"
|
|
27
|
+
self.status = status
|
|
28
|
+
self.response_body = body
|
|
29
|
+
headers.each { |key, value| response.headers[key] = value }
|
|
30
|
+
end
|
|
31
|
+
|
|
19
32
|
def resolve_audio_path(path)
|
|
20
33
|
return nil if path.blank?
|
|
21
34
|
|
|
@@ -141,6 +141,21 @@
|
|
|
141
141
|
playheads.forEach((el) => { el.style.left = `${pct}%`; });
|
|
142
142
|
};
|
|
143
143
|
|
|
144
|
+
// Seeking before the clip's metadata has loaded doesn't stick — it would
|
|
145
|
+
// start at 0:00 regardless of the scrubbed position. Defer the seek
|
|
146
|
+
// (recomputed from the then-current frame) until metadata is available.
|
|
147
|
+
const startAudio = (el) => {
|
|
148
|
+
const begin = () => {
|
|
149
|
+
if (timer === null) return; // stopped before metadata arrived
|
|
150
|
+
const { start, len } = audioWindow(el);
|
|
151
|
+
if (frame < start || frame >= start + len) return; // playhead left the window
|
|
152
|
+
el.currentTime = (frame - start) / fps;
|
|
153
|
+
el.play().catch(() => {});
|
|
154
|
+
};
|
|
155
|
+
if (el.readyState >= 1) begin();
|
|
156
|
+
else el.addEventListener("loadedmetadata", begin, { once: true });
|
|
157
|
+
};
|
|
158
|
+
|
|
144
159
|
// Audio is audible only while actually playing — seeking/scrubbing is
|
|
145
160
|
// silent. Each element plays only inside its own window, seeked to the
|
|
146
161
|
// matching offset, so you hear exactly what belongs at the current frame.
|
|
@@ -153,10 +168,7 @@
|
|
|
153
168
|
// playing, leave it on its own real-time clock: re-seeking every
|
|
154
169
|
// frame to chase the setInterval-driven frame counter (which lags
|
|
155
170
|
// real time) is what chops the audio.
|
|
156
|
-
if (el.paused)
|
|
157
|
-
el.currentTime = (currentFrame - start) / fps;
|
|
158
|
-
el.play().catch(() => {});
|
|
159
|
-
}
|
|
171
|
+
if (el.paused) startAudio(el);
|
|
160
172
|
} else if (!el.paused) {
|
|
161
173
|
el.pause();
|
|
162
174
|
}
|
|
@@ -264,6 +276,13 @@
|
|
|
264
276
|
}
|
|
265
277
|
});
|
|
266
278
|
|
|
279
|
+
// Turbo Drive swaps the body without unloading the window: detached
|
|
280
|
+
// <audio> elements keep playing and the playback loop keeps ticking.
|
|
281
|
+
// Stop this page's playback before navigating away or caching.
|
|
282
|
+
document.addEventListener("turbo:before-visit", stop, { once: true });
|
|
283
|
+
document.addEventListener("turbo:before-cache", stop, { once: true });
|
|
284
|
+
window.addEventListener("pagehide", stop, { once: true });
|
|
285
|
+
|
|
267
286
|
window.AnimateItStudio = {
|
|
268
287
|
setFrame,
|
|
269
288
|
currentFrame: () => frame
|
data/lib/animate_it/scene.rb
CHANGED
|
@@ -15,8 +15,10 @@ module AnimateIt
|
|
|
15
15
|
# ----- class-level DSL -----------------------------------------------
|
|
16
16
|
class << self
|
|
17
17
|
# Sidecar template name. Defaults to "canvas" — i.e. a scene whose
|
|
18
|
-
# composition is `MyHero` looks for `app/videos/my_hero/canvas.html
|
|
19
|
-
#
|
|
18
|
+
# composition is `MyHero` looks for `app/videos/my_hero/canvas.html.*`.
|
|
19
|
+
# The sidecar may be authored in HAML (`canvas.html.haml`) or ERB
|
|
20
|
+
# (`canvas.html.erb`); Rails resolves whichever exists. Override to point
|
|
21
|
+
# to a sibling template instead.
|
|
20
22
|
def template(name = nil)
|
|
21
23
|
@template = name.to_s if name
|
|
22
24
|
@template ||= "canvas"
|
data/lib/animate_it/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: animate_it
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- growth-constant
|
|
@@ -9,26 +9,45 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: haml
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.3'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: rails
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
15
29
|
requirements:
|
|
16
30
|
- - ">="
|
|
17
31
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
32
|
+
version: '7.2'
|
|
19
33
|
type: :runtime
|
|
20
34
|
prerelease: false
|
|
21
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
36
|
requirements:
|
|
23
37
|
- - ">="
|
|
24
38
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
39
|
+
version: '7.2'
|
|
26
40
|
description: |
|
|
27
|
-
AnimateIt
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
AnimateIt brings Remotion-style programmatic video to Ruby on Rails — without
|
|
42
|
+
React or a JavaScript project. Build videos as code: a Ruby class plus a HAML
|
|
43
|
+
template, using your app's own components, styles, fonts, and real data. Every
|
|
44
|
+
frame is a real web page, so anything you can render in your app you can put in
|
|
45
|
+
a video. Preview frame-by-frame in the bundled Studio, then export to
|
|
46
|
+
MP4/WebM/MOV/GIF with headless Chromium (Playwright) + FFmpeg.
|
|
47
|
+
|
|
48
|
+
Built for indie hackers and Rails developers who want to promote their projects
|
|
49
|
+
with polished product demos, launch clips, and social ads — without leaving
|
|
50
|
+
Ruby, hiring an editor, or learning After Effects.
|
|
32
51
|
executables:
|
|
33
52
|
- render_animate_it_video
|
|
34
53
|
extensions: []
|
|
@@ -106,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
106
125
|
requirements:
|
|
107
126
|
- - ">="
|
|
108
127
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '3.
|
|
128
|
+
version: '3.3'
|
|
110
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
130
|
requirements:
|
|
112
131
|
- - ">="
|
|
@@ -115,6 +134,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
115
134
|
requirements: []
|
|
116
135
|
rubygems_version: 3.6.9
|
|
117
136
|
specification_version: 4
|
|
118
|
-
summary:
|
|
119
|
-
|
|
137
|
+
summary: Remotion for Rails — make videos with your app's own assets, in Ruby. No
|
|
138
|
+
React required.
|
|
120
139
|
test_files: []
|