animate_it 0.3.2 → 0.4.0

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -1
  3. data/README.md +131 -19
  4. data/app/controllers/animate_it/frames_controller.rb +7 -0
  5. data/app/controllers/animate_it/public_players_controller.rb +69 -0
  6. data/app/controllers/animate_it/renders_controller.rb +3 -17
  7. data/app/controllers/animate_it/studio_controller.rb +1 -0
  8. data/app/jobs/animate_it/render_job.rb +2 -0
  9. data/app/views/animate_it/frames/filmstrip.html.haml +37 -6
  10. data/app/views/animate_it/frames/player.html.haml +81 -0
  11. data/app/views/animate_it/studio/_preview_pane.html.haml +1 -1
  12. data/app/views/animate_it/studio/_props_pane.html.haml +3 -2
  13. data/app/views/animate_it/studio/_studio_script.html.haml +71 -48
  14. data/app/views/animate_it/studio/show.html.haml +10 -3
  15. data/config/routes.rb +5 -0
  16. data/lib/animate_it/asset_manifest.rb +92 -0
  17. data/lib/animate_it/asset_renderer.rb +39 -7
  18. data/lib/animate_it/composition.rb +100 -9
  19. data/lib/animate_it/embed_helper.rb +22 -0
  20. data/lib/animate_it/engine.rb +4 -0
  21. data/lib/animate_it/runtime/runtime.js +385 -0
  22. data/lib/animate_it/runtime.rb +11 -0
  23. data/lib/animate_it/scene.rb +57 -3
  24. data/lib/animate_it/text_effects.rb +104 -0
  25. data/lib/animate_it/track_document_schema.rb +71 -0
  26. data/lib/animate_it/tracks/document.rb +100 -0
  27. data/lib/animate_it/tracks/layer.rb +13 -0
  28. data/lib/animate_it/tracks/recorder.rb +149 -0
  29. data/lib/animate_it/verification.rb +187 -0
  30. data/lib/animate_it/version.rb +1 -1
  31. data/lib/animate_it/video_renderer.rb +89 -26
  32. data/lib/animate_it/view_helpers.rb +11 -1
  33. data/lib/animate_it.rb +9 -0
  34. data/lib/tasks/animate_it_tasks.rake +133 -0
  35. metadata +13 -1
data/lib/animate_it.rb CHANGED
@@ -14,12 +14,21 @@ require_relative "animate_it/registry"
14
14
  require_relative "animate_it/render_store"
15
15
  require_relative "animate_it/beats"
16
16
  require_relative "animate_it/animation"
17
+ require_relative "animate_it/text_effects"
17
18
  require_relative "animate_it/scene"
18
19
  require_relative "animate_it/configuration"
19
20
  require_relative "animate_it/composition"
21
+ require_relative "animate_it/tracks/layer"
22
+ require_relative "animate_it/tracks/document"
23
+ require_relative "animate_it/tracks/recorder"
24
+ require_relative "animate_it/track_document_schema"
25
+ require_relative "animate_it/runtime"
26
+ require_relative "animate_it/embed_helper"
20
27
  require_relative "animate_it/output"
21
28
  require_relative "animate_it/video_renderer"
29
+ require_relative "animate_it/verification"
22
30
  require_relative "animate_it/asset_renderer"
31
+ require_relative "animate_it/asset_manifest"
23
32
  # Controllers (app/controllers/animate_it/*) and AnimateIt::RenderJob
24
33
  # (app/jobs/animate_it/render_job.rb) autoload via Rails — the engine's
25
34
  # `isolate_namespace AnimateIt` wires `app/` paths into the autoloader.
@@ -1,3 +1,5 @@
1
+ require "json"
2
+
1
3
  def animate_it_render_host
2
4
  # Prefer ANIMATE_IT_HOST; fall back to the legacy RAILS_MOTION_HOST, then
3
5
  # RAILS_HOST, then localhost.
@@ -8,6 +10,12 @@ def animate_it_render_host
8
10
  end
9
11
 
10
12
  namespace :animate_it do
13
+ desc "Validate source assets and provenance required by AnimateIt compositions"
14
+ task preflight: :environment do
15
+ result = AnimateIt::AssetManifest.new.validate!
16
+ puts "AnimateIt asset preflight passed (#{result.checked} assets)."
17
+ end
18
+
11
19
  desc <<~DESC
12
20
  Render a single composition's declared outputs to their asset paths.
13
21
 
@@ -52,6 +60,61 @@ namespace :animate_it do
52
60
  end
53
61
  end
54
62
 
63
+ desc <<~DESC
64
+ Pixel-diff a composition's client-driven /player page against the legacy
65
+ /filmstrip. Samples every <step>-th frame (default 10; use 1 for the full
66
+ gate) plus structural layer boundaries and adjacent frames.
67
+
68
+ Requires a running Rails server.
69
+
70
+ Usage:
71
+ bin/rails 'animate_it:verify[<composition-id>]'
72
+ bin/rails 'animate_it:verify[<composition-id>,1]'
73
+ ANIMATE_IT_PROPS_JSON='{"title":"Variant"}' bin/rails 'animate_it:verify[<composition-id>,1]'
74
+ ANIMATE_IT_PROPS_MATRIX_JSON='[{}, {"title":"Variant"}]' bin/rails 'animate_it:verify[<composition-id>,1]'
75
+
76
+ Set ANIMATE_IT_READY_TIMEOUT_MS to override the 30000ms readiness timeout.
77
+ DESC
78
+ task :verify, %i[id step] => :environment do |_task, args|
79
+ raise "Usage: bin/rails 'animate_it:verify[<composition-id>,(<step>)?]'" if args[:id].blank?
80
+
81
+ AnimateIt.load_compositions!
82
+ composition = AnimateIt.registry.fetch(args[:id])
83
+ verify_composition!(
84
+ composition,
85
+ host: animate_it_render_host,
86
+ step: (args[:step] || 10).to_i,
87
+ props_variants: verification_props_variants(composition)
88
+ )
89
+ end
90
+
91
+ desc <<~DESC
92
+ Every-frame verification gate for every client-driven composition and each
93
+ composition's declared `verification_props` variant.
94
+
95
+ Requires a running Rails server.
96
+
97
+ Usage:
98
+ bin/rails animate_it:verify_all
99
+ ANIMATE_IT_PROPS_MATRIX_JSON='[{}, {"title":"Variant"}]' bin/rails animate_it:verify_all
100
+ DESC
101
+ task verify_all: :environment do
102
+ AnimateIt.load_compositions!
103
+ compositions = AnimateIt.compositions.select(&:client_driven?)
104
+ raise "No client-driven AnimateIt compositions are registered" if compositions.empty?
105
+
106
+ compositions.each do |composition|
107
+ verify_composition!(
108
+ composition,
109
+ host: animate_it_render_host,
110
+ step: 1,
111
+ props_variants: verification_props_variants(composition)
112
+ )
113
+ end
114
+
115
+ puts "Verified #{compositions.size} client-driven compositions at every frame."
116
+ end
117
+
55
118
  # Parse `30..120` / `30-120` / `30` (single frame) into a Range. Returns
56
119
  # nil for blank input, meaning "use the whole composition".
57
120
  def parse_frame_range(raw, composition)
@@ -80,4 +143,74 @@ namespace :animate_it do
80
143
  $stdout.flush
81
144
  end
82
145
  end
146
+
147
+ def verification_props_variants(composition)
148
+ matrix_json = ENV.fetch("ANIMATE_IT_PROPS_MATRIX_JSON", nil)
149
+ single_json = ENV.fetch("ANIMATE_IT_PROPS_JSON", nil)
150
+ return parse_props_matrix(matrix_json) if matrix_json.present?
151
+ return [parse_props(single_json, "ANIMATE_IT_PROPS_JSON")] if single_json.present?
152
+
153
+ composition.verification_props
154
+ end
155
+
156
+ def parse_props_matrix(raw)
157
+ variants = JSON.parse(raw)
158
+ valid_matrix = variants.is_a?(Array) && variants.present? && variants.all?(Hash)
159
+ raise "ANIMATE_IT_PROPS_MATRIX_JSON must contain a non-empty JSON array of objects" unless valid_matrix
160
+
161
+ variants.map(&:deep_symbolize_keys)
162
+ rescue JSON::ParserError => e
163
+ raise "ANIMATE_IT_PROPS_MATRIX_JSON must contain a JSON array of objects: #{e.message}"
164
+ end
165
+
166
+ def parse_props(raw, name)
167
+ props = JSON.parse(raw)
168
+ raise "#{name} must contain a JSON object" unless props.is_a?(Hash)
169
+
170
+ props.deep_symbolize_keys
171
+ rescue JSON::ParserError => e
172
+ raise "#{name} must contain a JSON object: #{e.message}"
173
+ end
174
+
175
+ def verify_composition!(composition, host:, step:, props_variants:)
176
+ props_variants.each_with_index do |props, variant_index|
177
+ output_dir = Rails.root.join("tmp/animate_it/verify/#{composition.id}/variant-#{variant_index + 1}")
178
+ verification = AnimateIt::Verification.new(
179
+ composition:,
180
+ host:,
181
+ step:,
182
+ props:,
183
+ output_dir:,
184
+ ready_timeout: ENV.fetch("ANIMATE_IT_READY_TIMEOUT_MS", 30_000).to_i
185
+ )
186
+ puts "Verifying #{composition.id} variant #{variant_index + 1}/#{props_variants.size}: " \
187
+ "#{verification.sample_frames.size} frames (step #{verification.step}, " \
188
+ "RGB threshold #{verification.threshold}dB, alpha threshold #{verification.alpha_threshold}dB)"
189
+
190
+ results = verification.call
191
+ failures = results.reject(&:passed)
192
+ print_verification_results(results)
193
+ if failures.any?
194
+ raise "#{failures.size}/#{results.size} frames below threshold. " \
195
+ "Screenshots in #{verification.output_dir}"
196
+ end
197
+
198
+ puts "All #{results.size} frames match " \
199
+ "(RGB >= #{verification.threshold}dB, alpha >= #{verification.alpha_threshold}dB)."
200
+ end
201
+ end
202
+
203
+ def print_verification_results(results)
204
+ results.each do |result|
205
+ rgb = result.rgb_psnr.infinite? ? "identical" : format("%.2fdB", result.rgb_psnr)
206
+ alpha = result.alpha_psnr.infinite? ? "identical" : format("%.2fdB", result.alpha_psnr)
207
+ puts format(
208
+ " frame %<frame>5d RGB %<rgb>s alpha %<alpha>s%<flag>s",
209
+ frame: result.frame,
210
+ rgb:,
211
+ alpha:,
212
+ flag: result.passed ? "" : " ← FAIL"
213
+ )
214
+ end
215
+ end
83
216
  end
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.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - growth-constant
@@ -60,12 +60,14 @@ files:
60
60
  - app/controllers/animate_it/audio_controller.rb
61
61
  - app/controllers/animate_it/frames_controller.rb
62
62
  - app/controllers/animate_it/props_controller.rb
63
+ - app/controllers/animate_it/public_players_controller.rb
63
64
  - app/controllers/animate_it/renders_controller.rb
64
65
  - app/controllers/animate_it/studio_controller.rb
65
66
  - app/jobs/animate_it/application_job.rb
66
67
  - app/jobs/animate_it/render_job.rb
67
68
  - app/views/animate_it/frames/filmstrip.html.haml
68
69
  - app/views/animate_it/frames/fragment.html.haml
70
+ - app/views/animate_it/frames/player.html.haml
69
71
  - app/views/animate_it/frames/show.html.haml
70
72
  - app/views/animate_it/props/_form.html.haml
71
73
  - app/views/animate_it/props/update.html.haml
@@ -85,11 +87,13 @@ files:
85
87
  - lib/animate_it.rb
86
88
  - lib/animate_it/animation.rb
87
89
  - lib/animate_it/animation_helpers.rb
90
+ - lib/animate_it/asset_manifest.rb
88
91
  - lib/animate_it/asset_renderer.rb
89
92
  - lib/animate_it/beats.rb
90
93
  - lib/animate_it/composition.rb
91
94
  - lib/animate_it/configuration.rb
92
95
  - lib/animate_it/easing.rb
96
+ - lib/animate_it/embed_helper.rb
93
97
  - lib/animate_it/engine.rb
94
98
  - lib/animate_it/errors.rb
95
99
  - lib/animate_it/frame_context.rb
@@ -98,11 +102,19 @@ files:
98
102
  - lib/animate_it/props_schema.rb
99
103
  - lib/animate_it/registry.rb
100
104
  - lib/animate_it/render_store.rb
105
+ - lib/animate_it/runtime.rb
106
+ - lib/animate_it/runtime/runtime.js
101
107
  - lib/animate_it/scene.rb
102
108
  - lib/animate_it/style.rb
109
+ - lib/animate_it/text_effects.rb
103
110
  - lib/animate_it/timeline.rb
104
111
  - lib/animate_it/timing.rb
112
+ - lib/animate_it/track_document_schema.rb
113
+ - lib/animate_it/tracks/document.rb
114
+ - lib/animate_it/tracks/layer.rb
115
+ - lib/animate_it/tracks/recorder.rb
105
116
  - lib/animate_it/units.rb
117
+ - lib/animate_it/verification.rb
106
118
  - lib/animate_it/version.rb
107
119
  - lib/animate_it/video_renderer.rb
108
120
  - lib/animate_it/view_helpers.rb