@redwilly/anima 0.1.2 → 0.1.23

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.
package/dist/cli/index.js CHANGED
@@ -65,6 +65,7 @@ async function listScenes(file) {
65
65
 
66
66
  // src/cli/commands/render.ts
67
67
  import { Renderer, Resolution } from "@redwilly/anima";
68
+ import { join, dirname } from "path";
68
69
  async function render(file, options) {
69
70
  const loader = new SceneLoader();
70
71
  const { scenes, error } = await loader.load(file);
@@ -73,6 +74,7 @@ async function render(file, options) {
73
74
  process.exit(1);
74
75
  }
75
76
  let scene;
77
+ let sceneName;
76
78
  if (options.scene) {
77
79
  const found = scenes.get(options.scene);
78
80
  if (!found) {
@@ -84,9 +86,12 @@ async function render(file, options) {
84
86
  process.exit(1);
85
87
  }
86
88
  scene = found;
89
+ sceneName = options.scene;
87
90
  } else {
88
91
  if (scenes.size === 1) {
89
- scene = scenes.values().next().value;
92
+ const entry = scenes.entries().next().value;
93
+ sceneName = entry[0];
94
+ scene = entry[1];
90
95
  } else {
91
96
  console.error("Error: Multiple scenes found in file. Please specify one with --scene.");
92
97
  for (const name of scenes.keys()) {
@@ -99,8 +104,10 @@ async function render(file, options) {
99
104
  console.error("Error: No scene found to render.");
100
105
  process.exit(1);
101
106
  }
107
+ const format = options.format ?? "mp4";
108
+ const outputPath = options.output ?? join("media", `${sceneName}.${format}`);
109
+ const cacheDir = join(dirname(outputPath), ".anima-cache");
102
110
  const renderer = new Renderer();
103
- const outputPath = options.output ?? `output.${options.format ?? "mp4"}`;
104
111
  let width = scene.getWidth();
105
112
  let height = scene.getHeight();
106
113
  if (options.resolution) {
@@ -113,13 +120,14 @@ async function render(file, options) {
113
120
  console.warn(`Warning: Resolution preset '${options.resolution}' not found. Using scene defaults.`);
114
121
  }
115
122
  }
116
- console.log(`Rendering scene to '${outputPath}'...`);
123
+ console.log(`Rendering scene '${sceneName}' to '${outputPath}'...`);
117
124
  await renderer.render(scene, outputPath, {
118
125
  width,
119
126
  height,
120
127
  frameRate: options.fps ? parseInt(options.fps, 10) : void 0,
121
- format: options.format,
128
+ format,
122
129
  quality: options.quality,
130
+ cacheDir,
123
131
  onProgress: (progress) => {
124
132
  const percent = progress.percentage.toFixed(1);
125
133
  const eta = (progress.estimatedRemainingMs / 1e3).toFixed(1);
package/dist/index.d.ts CHANGED
@@ -275,6 +275,7 @@ declare class AnimationQueue {
275
275
  enqueueAnimation(animation: Animation<Mobject>): void;
276
276
  setLastDuration(seconds: number): void;
277
277
  setLastEasing(easing: EasingFunction): void;
278
+ setLastDelay(seconds: number): void;
278
279
  isEmpty(): boolean;
279
280
  popLastAnimation(): Animation<Mobject> | null;
280
281
  toAnimation(): Animation<Mobject>;
@@ -343,6 +344,7 @@ declare class Mobject {
343
344
  };
344
345
  duration(seconds: number): this;
345
346
  ease(easing: EasingFunction): this;
347
+ delay(seconds: number): this;
346
348
  toAnimation(): Animation<Mobject>;
347
349
  getQueuedDuration(): number;
348
350
  hasQueuedAnimations(): boolean;
@@ -356,6 +358,11 @@ declare class Mobject {
356
358
  * ).fadeOut(1);
357
359
  */
358
360
  parallel(...items: (Animation<Mobject> | Mobject)[]): this;
361
+ /**
362
+ * Computes a CRC32 hash of this mobject's full state.
363
+ * Used by the segment cache to detect changes.
364
+ */
365
+ computeHash(): number;
359
366
  }
360
367
 
361
368
  /**
@@ -414,6 +421,12 @@ declare abstract class Animation<T extends Mobject = Mobject> {
414
421
  */
415
422
  reset(): void;
416
423
  update(progress: number): void;
424
+ /**
425
+ * Hashes the animation type, config, and target state.
426
+ * Subclass-specific behavior is captured through the target's hash,
427
+ * since animations mutate the target.
428
+ */
429
+ computeHash(): number;
417
430
  }
418
431
 
419
432
  /**
@@ -914,6 +927,10 @@ declare class Camera {
914
927
  */
915
928
  isInView(pos: Vector2): boolean;
916
929
  reset(): this;
930
+ /**
931
+ * Hashes camera config and the CameraFrame's full transform state.
932
+ */
933
+ computeHash(): number;
917
934
  }
918
935
 
919
936
  /**
@@ -930,6 +947,26 @@ interface SceneConfig {
930
947
  readonly frameRate?: number;
931
948
  }
932
949
 
950
+ /**
951
+ * A Segment represents one independent rendering unit,
952
+ * corresponding to a single play() or wait() call.
953
+ *
954
+ * Its hash is a holistic CRC32 composition of the camera state,
955
+ * all current mobjects, and the animations for this segment.
956
+ */
957
+ interface Segment {
958
+ /** Zero-based index in the scene's segment list. */
959
+ readonly index: number;
960
+ /** Start time in seconds. */
961
+ readonly startTime: number;
962
+ /** End time in seconds. */
963
+ readonly endTime: number;
964
+ /** Animations scheduled in this segment (empty for wait segments). */
965
+ readonly animations: readonly Animation[];
966
+ /** CRC32 hash of camera + mobjects + animations at this point. */
967
+ readonly hash: number;
968
+ }
969
+
933
970
  /**
934
971
  * Scene is the core container that manages Mobjects and coordinates animations.
935
972
  * It provides both a simple API for playing animations and access to the
@@ -940,6 +977,7 @@ declare class Scene {
940
977
  private readonly mobjects;
941
978
  private readonly timeline;
942
979
  private readonly _camera;
980
+ private readonly segmentList;
943
981
  private playheadTime;
944
982
  constructor(config?: SceneConfig);
945
983
  get camera(): Camera;
@@ -1017,16 +1055,36 @@ declare class Scene {
1017
1055
  * Camera calculates Manim-compatible frame dimensions from pixel resolution.
1018
1056
  */
1019
1057
  getCamera(): Camera;
1058
+ /**
1059
+ * Get the list of segments emitted by play() and wait() calls.
1060
+ * Used by the Renderer for cache-aware segmented rendering.
1061
+ */
1062
+ getSegments(): readonly Segment[];
1020
1063
  /**
1021
1064
  * Validates and registers animation targets based on lifecycle.
1022
1065
  * Handles composition animations (Sequence, Parallel) by processing children.
1023
1066
  */
1024
1067
  private validateAndRegisterAnimation;
1068
+ /**
1069
+ * Checks if a Mobject is in the scene.
1070
+ * An object is "in scene" if:
1071
+ * - It's directly registered in this.mobjects, OR
1072
+ * - Any ancestor in its parent chain is registered
1073
+ *
1074
+ * This respects the scene graph hierarchy - children of registered
1075
+ * VGroups are implicitly in scene via their parent.
1076
+ */
1077
+ isInScene(mobject: Mobject): boolean;
1025
1078
  /**
1026
1079
  * Gets children of composition animations (Sequence, Parallel).
1027
1080
  * Returns empty array for non-composition animations.
1028
1081
  */
1029
1082
  private getAnimationChildren;
1083
+ /**
1084
+ * Computes a holistic CRC32 hash for a segment.
1085
+ * Includes camera state, all current mobjects, animations, and timing.
1086
+ */
1087
+ private computeSegmentHash;
1030
1088
  }
1031
1089
 
1032
1090
  type PathCommandType = 'Move' | 'Line' | 'Quadratic' | 'Cubic' | 'Close';
@@ -1174,6 +1232,11 @@ declare class VMobject extends Mobject {
1174
1232
  draw(durationSeconds?: number): this & {
1175
1233
  toAnimation(): Animation<Mobject>;
1176
1234
  };
1235
+ /**
1236
+ * Extends parent hash with VMobject-specific state:
1237
+ * stroke/fill colors, widths, opacity, and path geometry.
1238
+ */
1239
+ computeHash(): number;
1177
1240
  }
1178
1241
 
1179
1242
  type CornerPosition = 'TOP_LEFT' | 'TOP_RIGHT' | 'BOTTOM_LEFT' | 'BOTTOM_RIGHT';
@@ -1227,6 +1290,11 @@ declare class VGroup extends VMobject {
1227
1290
  toCorner(corner: CornerPosition, buff?: number): this;
1228
1291
  arrange(direction?: Direction, buff?: number, shouldCenter?: boolean): this;
1229
1292
  alignTo(target: VMobject, edge: Edge): this;
1293
+ /**
1294
+ * Recursively hashes this VGroup and all children.
1295
+ * Any child state change invalidates segments containing this group.
1296
+ */
1297
+ computeHash(): number;
1230
1298
  }
1231
1299
 
1232
1300
  declare class Arc extends VMobject {
@@ -1248,13 +1316,9 @@ declare class Line extends VMobject {
1248
1316
  private generatePath;
1249
1317
  }
1250
1318
 
1251
- declare class Point extends Circle {
1252
- constructor(location?: Vector2);
1253
- }
1254
-
1255
1319
  declare class Polygon extends VMobject {
1256
1320
  readonly vertices: Vector2[];
1257
- constructor(vertices: Vector2[]);
1321
+ constructor(...points: Array<[number, number]>);
1258
1322
  private generatePath;
1259
1323
  }
1260
1324
 
@@ -1281,28 +1345,26 @@ declare class Glyph extends VMobject {
1281
1345
  constructor(fontkitGlyph: Glyph$1, character: string, scale: number, offsetX: number, offsetY: number);
1282
1346
  }
1283
1347
 
1284
- /**
1285
- * Options for configuring Text appearance.
1286
- */
1287
- interface TextStyle {
1288
- fontSize: number;
1289
- color: Color;
1290
- }
1291
-
1292
1348
  /**
1293
1349
  * A VGroup of vectorized glyphs created from a text string using fontkit.
1294
1350
  * Each character becomes a Glyph VMobject that can be individually animated.
1351
+ *
1352
+ * Uses VMobject's fill/stroke as the source of truth (same as geometry).
1353
+ * Default: white fill, white stroke width 2.
1295
1354
  */
1296
1355
  declare class Text extends VGroup {
1297
1356
  readonly text: string;
1298
- private style;
1357
+ private fontSize;
1299
1358
  private fontPath;
1300
- constructor(text: string, fontPath: string, options?: Partial<TextStyle>);
1359
+ constructor(text: string, fontPath?: string, options?: {
1360
+ fontSize?: number;
1361
+ });
1301
1362
  private buildGlyphs;
1302
- private getCharacterForGlyph;
1303
- private applyStyle;
1304
- setStyle(options: Partial<TextStyle>): this;
1305
- getStyle(): TextStyle;
1363
+ /** Propagates this Text's fill/stroke to all Glyph children. */
1364
+ private propagate;
1365
+ stroke(color: Color, width?: number): this;
1366
+ fill(color: Color, opacity?: number): this;
1367
+ getFontSize(): number;
1306
1368
  getGlyph(index: number): Glyph | undefined;
1307
1369
  }
1308
1370
 
@@ -1578,66 +1640,61 @@ declare class MorphTo<T extends VMobject = VMobject> extends TransformativeAnima
1578
1640
  }
1579
1641
 
1580
1642
  /**
1581
- * Animation that first draws the border progressively, then fills the shape.
1582
- * The first 50% of the animation draws the stroke, the second 50% fades in the fill.
1643
+ * Animation that first draws the stroke progressively, then fades in the fill.
1644
+ * - First 50%: stroke draws progressively
1645
+ * - Second 50%: fill fades in
1583
1646
  *
1584
- * Supports VGroup (including Text): animates each child's paths progressively.
1647
+ * - Single VMobject: stroke then fill
1648
+ * - VGroup: all children animate together
1649
+ * - Text (VGroup of Glyphs): Glyphs animate sequentially for handwriting effect
1585
1650
  *
1586
1651
  * This is an introductory animation - it auto-registers the target with the scene.
1587
- *
1588
- * @example
1589
- * const rect = new Rectangle(2, 1);
1590
- * scene.play(new Draw(rect)); // Border draws, then fill fades in
1591
1652
  */
1592
1653
  declare class Draw<T extends VMobject = VMobject> extends IntroductoryAnimation<T> {
1593
- private readonly isVGroup;
1594
- /** For non-VGroup targets: original paths on the target itself. */
1595
1654
  private readonly originalPaths;
1596
- private readonly originalFillOpacity;
1597
1655
  private readonly originalOpacity;
1598
- /** For VGroup targets: original state of each child. */
1656
+ private readonly originalStrokeColor;
1657
+ private readonly originalStrokeWidth;
1658
+ private readonly originalFillColor;
1659
+ private readonly originalFillOpacity;
1599
1660
  private readonly childStates;
1661
+ /** Glyph states for Text children, keyed by the Text VMobject reference. */
1662
+ private readonly glyphStates;
1600
1663
  constructor(target: T);
1601
- /** Interpolates stroke drawing (0-0.5) and then fill fade (0.5-1). */
1664
+ private createState;
1602
1665
  interpolate(progress: number): void;
1603
- /** Interpolates a single VMobject (non-VGroup). */
1604
- private interpolateVMobject;
1605
- /** Interpolates a VGroup by animating each child's paths. */
1606
1666
  private interpolateVGroup;
1667
+ private interpolateGlyphs;
1668
+ /** Interpolates a single VMobject: stroke (0-0.5), then fill (0.5-1). */
1669
+ private interpolateVMobject;
1607
1670
  }
1608
1671
 
1609
1672
  /**
1610
- * Animation that progressively draws a VMobject's paths from start to end.
1611
- * Preserves both stroke and fill properties - the complete object is visible at the end.
1612
- *
1613
- * This is the canonical animation for progressive path drawing.
1614
- * At progress 0, nothing is shown. At progress 1, the complete path is shown.
1673
+ * Animation that progressively draws VMobject paths from start to end.
1615
1674
  *
1616
- * Supports VGroup (including Text): animates each child's paths progressively.
1675
+ * - Single VMobject: paths draw progressively
1676
+ * - VGroup: all children animate together with same progress
1677
+ * - Text (VGroup of Glyphs): Glyphs animate sequentially for handwriting effect
1617
1678
  *
1618
1679
  * This is an introductory animation - it auto-registers the target with the scene.
1619
- *
1620
- * @example
1621
- * const circle = new Circle(1);
1622
- * scene.play(new Write(circle)); // Circle is drawn progressively
1623
- *
1624
- * const text = new Text('Hello');
1625
- * scene.play(new Write(text)); // Text is written progressively
1626
1680
  */
1627
1681
  declare class Write<T extends VMobject = VMobject> extends IntroductoryAnimation<T> {
1628
- private readonly isVGroup;
1629
- /** For non-VGroup targets: original paths on the target itself. */
1630
1682
  private readonly originalPaths;
1631
1683
  private readonly originalOpacity;
1684
+ private readonly originalStrokeColor;
1685
+ private readonly originalStrokeWidth;
1686
+ private readonly originalFillColor;
1632
1687
  private readonly originalFillOpacity;
1633
- /** For VGroup targets: original state of each child. */
1634
1688
  private readonly childStates;
1689
+ /** Glyph states for Text children, keyed by the Text VMobject reference. */
1690
+ private readonly glyphStates;
1635
1691
  constructor(target: T);
1692
+ private createState;
1636
1693
  interpolate(progress: number): void;
1637
- /** Interpolates a single VMobject (non-VGroup). */
1638
- private interpolateVMobject;
1639
- /** Interpolates a VGroup by animating each child's paths. */
1640
1694
  private interpolateVGroup;
1695
+ private interpolateGlyphs;
1696
+ /** Applies progress to a single VMobject, updating its paths and style. */
1697
+ private applyProgress;
1641
1698
  }
1642
1699
 
1643
1700
  /**
@@ -1901,21 +1958,6 @@ declare class Shake extends TransformativeAnimation<CameraFrame> {
1901
1958
  private noise;
1902
1959
  }
1903
1960
 
1904
- /**
1905
- * Scene serialization - the main entry point for serializing/deserializing Scenes.
1906
- */
1907
-
1908
- /**
1909
- * Serializes a Scene into a JSON-formatted string.
1910
- *
1911
- * Useful for saving scene state to a file or transmitting it over a network.
1912
- */
1913
- declare function serialize(scene: Scene): string;
1914
- /**
1915
- * Restores a complete Scene from a JSON-formatted string.
1916
- */
1917
- declare function deserialize(json: string): Scene;
1918
-
1919
1961
  /**
1920
1962
  * Supported render output formats.
1921
1963
  * - sprite: PNG sequence (frame_0000.png, frame_0001.png, ...)
@@ -1972,6 +2014,10 @@ interface RenderConfig {
1972
2014
  quality?: RenderQuality;
1973
2015
  /** Progress callback for render updates. */
1974
2016
  onProgress?: ProgressCallback;
2017
+ /** Enable segment caching for incremental rendering. Default: true for video formats. */
2018
+ cache?: boolean;
2019
+ /** Custom cache directory path. Default: '.anima-cache' relative to output. */
2020
+ cacheDir?: string;
1975
2021
  }
1976
2022
  /**
1977
2023
  * Progress information during rendering.
@@ -1995,12 +2041,17 @@ type ProgressCallback = (progress: RenderProgress) => void;
1995
2041
 
1996
2042
  /**
1997
2043
  * Main renderer for producing output from Scenes.
1998
- * Supports multiple output formats and provides progress callbacks.
2044
+ * Supports multiple output formats, progress callbacks, and
2045
+ * segment-level caching for incremental re-renders.
1999
2046
  */
2000
2047
  declare class Renderer {
2001
2048
  /**
2002
2049
  * Renders a scene to the specified output.
2003
2050
  *
2051
+ * For video formats (mp4/webp/gif) with caching enabled, renders
2052
+ * each segment independently and concatenates the results.
2053
+ * Segments whose hash matches a cached file are skipped entirely.
2054
+ *
2004
2055
  * @param scene The scene to render
2005
2056
  * @param outputPath Output file or directory path (depends on format)
2006
2057
  * @param config Optional render configuration
@@ -2026,6 +2077,20 @@ declare class Renderer {
2026
2077
  * Renders a single frame (the last frame of the animation).
2027
2078
  */
2028
2079
  private renderSingleFrame;
2080
+ /**
2081
+ * Cache-aware segmented rendering.
2082
+ *
2083
+ * For each segment:
2084
+ * 1. Check if a cached partial file exists (hash match)
2085
+ * 2. If miss, render that segment's frame range to a partial .mp4
2086
+ * 3. After all segments, concat partial files into final output
2087
+ * 4. Prune orphaned cache entries
2088
+ */
2089
+ private renderSegmented;
2090
+ /**
2091
+ * Renders a single segment's frame range to a video file.
2092
+ */
2093
+ private renderSegmentToFile;
2029
2094
  }
2030
2095
 
2031
2096
  /**
@@ -2083,4 +2148,33 @@ declare class ProgressReporter {
2083
2148
  complete(): void;
2084
2149
  }
2085
2150
 
2086
- export { Animation, type AnimationConfig, Arc, Arrow, Camera, type CameraConfig, CameraFrame, Circle, Color, Draw, type EasingFunction, type EdgeConfig, FadeIn, FadeOut, Follow, FrameRenderer, Glyph, Graph, GraphEdge, GraphNode, type GraphNodeId, type Keyframe, KeyframeAnimation, KeyframeTrack, type LayoutConfig, type LayoutType, Line, Mobject, MorphTo, MoveTo, type NodeConfig, Parallel, Point, Polygon, type ProgressCallback, ProgressReporter, Rectangle, type RenderConfig, type RenderFormat, type RenderProgress, type RenderQuality, Renderer, Resolution, type ResolvedCameraConfig, Rotate, Scale, Scene, type SceneConfig, type ScheduledAnimation, Sequence, Shake, Text, type TextStyle, Timeline, type TimelineConfig, Unwrite, VGroup, VMobject, Vector2, Write, clearRegistry, smooth as defaultEasing, deserialize, doubleSmooth, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, exponentialDecay, getEasing, hasEasing, linear, lingering, notQuiteThere, registerEasing, runningStart, rushFrom, rushInto, serialize, slowInto, smooth, thereAndBack, thereAndBackWithPause, unregisterEasing, wiggle };
2151
+ /** Protocol for objects that contribute to segment hashing. */
2152
+ interface Hashable {
2153
+ computeHash(): number;
2154
+ }
2155
+
2156
+ /**
2157
+ * Manages a disk-based cache of rendered segment partial files.
2158
+ *
2159
+ * Each segment is stored as a video file keyed by its CRC32 hash.
2160
+ * On re-render, segments whose hashes match an existing file are skipped.
2161
+ */
2162
+ declare class SegmentCache {
2163
+ private readonly cacheDir;
2164
+ constructor(cacheDir: string);
2165
+ /** Ensure the cache directory exists. */
2166
+ init(): Promise<void>;
2167
+ /** Check if a rendered segment file exists for the given hash. */
2168
+ has(hash: number): boolean;
2169
+ /** Get the absolute file path for a segment hash. */
2170
+ getPath(hash: number): string;
2171
+ /** Get the cache directory path. */
2172
+ getDir(): string;
2173
+ /**
2174
+ * Remove cached files that are not in the active set.
2175
+ * Call after a full render to clean up stale segments.
2176
+ */
2177
+ prune(activeHashes: Set<number>): Promise<void>;
2178
+ }
2179
+
2180
+ export { Animation, type AnimationConfig, Arc, Arrow, Camera, type CameraConfig, CameraFrame, Circle, Color, Draw, type EasingFunction, type EdgeConfig, FadeIn, FadeOut, Follow, FrameRenderer, Glyph, Graph, GraphEdge, GraphNode, type GraphNodeId, type Hashable, type Keyframe, KeyframeAnimation, KeyframeTrack, type LayoutConfig, type LayoutType, Line, Mobject, MorphTo, MoveTo, type NodeConfig, Parallel, Polygon, type ProgressCallback, ProgressReporter, Rectangle, type RenderConfig, type RenderFormat, type RenderProgress, type RenderQuality, Renderer, Resolution, type ResolvedCameraConfig, Rotate, Scale, Scene, type SceneConfig, type ScheduledAnimation, type Segment, SegmentCache, Sequence, Shake, Text, Timeline, type TimelineConfig, Unwrite, VGroup, VMobject, Vector2, Write, clearRegistry, smooth as defaultEasing, doubleSmooth, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, exponentialDecay, getEasing, hasEasing, linear, lingering, notQuiteThere, registerEasing, runningStart, rushFrom, rushInto, slowInto, smooth, thereAndBack, thereAndBackWithPause, unregisterEasing, wiggle };