@mihirsarya/manim-scroll-next 0.1.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.
- package/dist/cache.d.ts +63 -0
- package/dist/cache.js +206 -0
- package/dist/cache.test.d.ts +1 -0
- package/dist/cache.test.js +292 -0
- package/dist/extractor.d.ts +29 -0
- package/dist/extractor.js +293 -0
- package/dist/extractor.test.d.ts +1 -0
- package/dist/extractor.test.js +288 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +180 -0
- package/dist/process-cli.d.ts +6 -0
- package/dist/process-cli.js +128 -0
- package/dist/renderer.d.ts +30 -0
- package/dist/renderer.js +235 -0
- package/package.json +27 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.renderAnimations = exports.cleanOrphanedCache = exports.readCacheManifest = exports.writeCacheManifest = exports.getAnimationsToRender = exports.getCacheEntry = exports.isCached = exports.computePropsHash = exports.extractAnimations = void 0;
|
|
37
|
+
exports.withManimScroll = withManimScroll;
|
|
38
|
+
exports.processManimScroll = processManimScroll;
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const extractor_1 = require("./extractor");
|
|
41
|
+
const cache_1 = require("./cache");
|
|
42
|
+
const renderer_1 = require("./renderer");
|
|
43
|
+
let hasProcessed = false;
|
|
44
|
+
/**
|
|
45
|
+
* Process ManimScroll components: extract, cache, and render.
|
|
46
|
+
*/
|
|
47
|
+
async function processManimScroll(projectDir, config) {
|
|
48
|
+
var _a;
|
|
49
|
+
if (hasProcessed) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
hasProcessed = true;
|
|
53
|
+
const publicDir = path.join(projectDir, "public");
|
|
54
|
+
const verbose = (_a = config.verbose) !== null && _a !== void 0 ? _a : false;
|
|
55
|
+
if (verbose) {
|
|
56
|
+
console.log("[manim-scroll] Scanning for ManimScroll components...");
|
|
57
|
+
}
|
|
58
|
+
// Extract all ManimScroll usages from source files
|
|
59
|
+
const animations = await (0, extractor_1.extractAnimations)({
|
|
60
|
+
rootDir: projectDir,
|
|
61
|
+
include: config.include,
|
|
62
|
+
exclude: config.exclude,
|
|
63
|
+
});
|
|
64
|
+
if (animations.length === 0) {
|
|
65
|
+
if (verbose) {
|
|
66
|
+
console.log("[manim-scroll] No ManimScroll components found.");
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (verbose) {
|
|
71
|
+
console.log(`[manim-scroll] Found ${animations.length} ManimScroll component(s).`);
|
|
72
|
+
for (const animation of animations) {
|
|
73
|
+
const hash = (0, cache_1.computePropsHash)(animation.scene, animation.props);
|
|
74
|
+
console.log(` - ${animation.id} (hash: ${hash})`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Determine which need rendering
|
|
78
|
+
const { cached, toRender } = (0, cache_1.getAnimationsToRender)(animations, publicDir);
|
|
79
|
+
if (verbose && cached.length > 0) {
|
|
80
|
+
console.log(`[manim-scroll] ${cached.length} animation(s) already cached.`);
|
|
81
|
+
}
|
|
82
|
+
// Render new animations
|
|
83
|
+
if (toRender.length > 0) {
|
|
84
|
+
console.log(`[manim-scroll] Rendering ${toRender.length} animation(s)...`);
|
|
85
|
+
const renderOptions = {
|
|
86
|
+
pythonPath: config.pythonPath,
|
|
87
|
+
cliPath: config.cliPath,
|
|
88
|
+
templatesDir: config.templatesDir,
|
|
89
|
+
concurrency: config.concurrency,
|
|
90
|
+
fps: config.fps,
|
|
91
|
+
resolution: config.resolution,
|
|
92
|
+
quality: config.quality,
|
|
93
|
+
format: config.format,
|
|
94
|
+
};
|
|
95
|
+
const results = await (0, renderer_1.renderAnimations)(toRender, publicDir, renderOptions);
|
|
96
|
+
const failed = results.filter((r) => !r.success);
|
|
97
|
+
if (failed.length > 0) {
|
|
98
|
+
console.error(`[manim-scroll] Warning: ${failed.length} animation(s) failed to render.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (verbose) {
|
|
102
|
+
console.log("[manim-scroll] All animations are cached, skipping render.");
|
|
103
|
+
}
|
|
104
|
+
// Write the cache manifest for runtime lookup
|
|
105
|
+
(0, cache_1.writeCacheManifest)(animations, publicDir);
|
|
106
|
+
// Clean up orphaned cache entries
|
|
107
|
+
if (config.cleanOrphans !== false) {
|
|
108
|
+
(0, cache_1.cleanOrphanedCache)(animations, publicDir);
|
|
109
|
+
}
|
|
110
|
+
console.log("[manim-scroll] Build complete.");
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Wrap a Next.js config with ManimScroll build-time processing.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```js
|
|
117
|
+
* // next.config.js
|
|
118
|
+
* const { withManimScroll } = require("@mihirsarya/manim-scroll-next");
|
|
119
|
+
*
|
|
120
|
+
* module.exports = withManimScroll({
|
|
121
|
+
* manimScroll: {
|
|
122
|
+
* pythonPath: "python3",
|
|
123
|
+
* quality: "h",
|
|
124
|
+
* },
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
function withManimScroll(nextConfig = {}) {
|
|
129
|
+
var _a;
|
|
130
|
+
const manimConfig = (_a = nextConfig.manimScroll) !== null && _a !== void 0 ? _a : {};
|
|
131
|
+
// Remove manimScroll from the config passed to Next.js
|
|
132
|
+
const { manimScroll: _, ...restConfig } = nextConfig;
|
|
133
|
+
return {
|
|
134
|
+
...restConfig,
|
|
135
|
+
webpack(config, context) {
|
|
136
|
+
// Only process on the server build to avoid double processing
|
|
137
|
+
if (context.isServer && !context.dev) {
|
|
138
|
+
// Run processing before webpack starts
|
|
139
|
+
const projectDir = context.dir;
|
|
140
|
+
// We need to run this synchronously before webpack continues
|
|
141
|
+
// Using a sync wrapper around the async function
|
|
142
|
+
const { execSync } = require("child_process");
|
|
143
|
+
const scriptPath = path.join(__dirname, "process-cli.js");
|
|
144
|
+
// Check if the CLI script exists, if not we'll inline the processing
|
|
145
|
+
if (require("fs").existsSync(scriptPath)) {
|
|
146
|
+
try {
|
|
147
|
+
execSync(`node "${scriptPath}" --project-dir "${projectDir}" --config '${JSON.stringify(manimConfig)}'`, { stdio: "inherit" });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error("[manim-scroll] Error during build:", error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Run in dev mode on first build
|
|
155
|
+
if (context.dev && context.isServer) {
|
|
156
|
+
processManimScroll(context.dir, manimConfig).catch((error) => {
|
|
157
|
+
console.error("[manim-scroll] Error during dev processing:", error);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// Call the original webpack config if provided
|
|
161
|
+
if (typeof restConfig.webpack === "function") {
|
|
162
|
+
return restConfig.webpack(config, context);
|
|
163
|
+
}
|
|
164
|
+
return config;
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// Export types and utilities for advanced usage
|
|
169
|
+
var extractor_2 = require("./extractor");
|
|
170
|
+
Object.defineProperty(exports, "extractAnimations", { enumerable: true, get: function () { return extractor_2.extractAnimations; } });
|
|
171
|
+
var cache_2 = require("./cache");
|
|
172
|
+
Object.defineProperty(exports, "computePropsHash", { enumerable: true, get: function () { return cache_2.computePropsHash; } });
|
|
173
|
+
Object.defineProperty(exports, "isCached", { enumerable: true, get: function () { return cache_2.isCached; } });
|
|
174
|
+
Object.defineProperty(exports, "getCacheEntry", { enumerable: true, get: function () { return cache_2.getCacheEntry; } });
|
|
175
|
+
Object.defineProperty(exports, "getAnimationsToRender", { enumerable: true, get: function () { return cache_2.getAnimationsToRender; } });
|
|
176
|
+
Object.defineProperty(exports, "writeCacheManifest", { enumerable: true, get: function () { return cache_2.writeCacheManifest; } });
|
|
177
|
+
Object.defineProperty(exports, "readCacheManifest", { enumerable: true, get: function () { return cache_2.readCacheManifest; } });
|
|
178
|
+
Object.defineProperty(exports, "cleanOrphanedCache", { enumerable: true, get: function () { return cache_2.cleanOrphanedCache; } });
|
|
179
|
+
var renderer_2 = require("./renderer");
|
|
180
|
+
Object.defineProperty(exports, "renderAnimations", { enumerable: true, get: function () { return renderer_2.renderAnimations; } });
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* CLI script for processing ManimScroll components.
|
|
5
|
+
* This is called during the Next.js build process.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const extractor_1 = require("./extractor");
|
|
43
|
+
const cache_1 = require("./cache");
|
|
44
|
+
const renderer_1 = require("./renderer");
|
|
45
|
+
async function main() {
|
|
46
|
+
var _a;
|
|
47
|
+
const args = process.argv.slice(2);
|
|
48
|
+
let projectDir = process.cwd();
|
|
49
|
+
let config = {};
|
|
50
|
+
// Parse arguments
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
const arg = args[i];
|
|
53
|
+
if (arg === "--project-dir" && args[i + 1]) {
|
|
54
|
+
projectDir = args[++i];
|
|
55
|
+
}
|
|
56
|
+
else if (arg === "--config" && args[i + 1]) {
|
|
57
|
+
try {
|
|
58
|
+
config = JSON.parse(args[++i]);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
console.error("Invalid JSON config");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const publicDir = path.join(projectDir, "public");
|
|
67
|
+
const verbose = (_a = config.verbose) !== null && _a !== void 0 ? _a : false;
|
|
68
|
+
if (verbose) {
|
|
69
|
+
console.log("[manim-scroll] Scanning for ManimScroll components...");
|
|
70
|
+
}
|
|
71
|
+
// Extract all ManimScroll usages
|
|
72
|
+
const animations = await (0, extractor_1.extractAnimations)({
|
|
73
|
+
rootDir: projectDir,
|
|
74
|
+
include: config.include,
|
|
75
|
+
exclude: config.exclude,
|
|
76
|
+
});
|
|
77
|
+
if (animations.length === 0) {
|
|
78
|
+
if (verbose) {
|
|
79
|
+
console.log("[manim-scroll] No ManimScroll components found.");
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (verbose) {
|
|
84
|
+
console.log(`[manim-scroll] Found ${animations.length} ManimScroll component(s).`);
|
|
85
|
+
for (const animation of animations) {
|
|
86
|
+
const hash = (0, cache_1.computePropsHash)(animation.scene, animation.props);
|
|
87
|
+
console.log(` - ${animation.id} (hash: ${hash})`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Determine which need rendering
|
|
91
|
+
const { cached, toRender } = (0, cache_1.getAnimationsToRender)(animations, publicDir);
|
|
92
|
+
if (verbose && cached.length > 0) {
|
|
93
|
+
console.log(`[manim-scroll] ${cached.length} animation(s) already cached.`);
|
|
94
|
+
}
|
|
95
|
+
// Render new animations
|
|
96
|
+
if (toRender.length > 0) {
|
|
97
|
+
console.log(`[manim-scroll] Rendering ${toRender.length} animation(s)...`);
|
|
98
|
+
const renderOptions = {
|
|
99
|
+
pythonPath: config.pythonPath,
|
|
100
|
+
cliPath: config.cliPath,
|
|
101
|
+
templatesDir: config.templatesDir,
|
|
102
|
+
concurrency: config.concurrency,
|
|
103
|
+
fps: config.fps,
|
|
104
|
+
resolution: config.resolution,
|
|
105
|
+
quality: config.quality,
|
|
106
|
+
format: config.format,
|
|
107
|
+
};
|
|
108
|
+
const results = await (0, renderer_1.renderAnimations)(toRender, publicDir, renderOptions);
|
|
109
|
+
const failed = results.filter((r) => !r.success);
|
|
110
|
+
if (failed.length > 0) {
|
|
111
|
+
console.error(`[manim-scroll] Warning: ${failed.length} animation(s) failed to render.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (verbose) {
|
|
115
|
+
console.log("[manim-scroll] All animations are cached, skipping render.");
|
|
116
|
+
}
|
|
117
|
+
// Write the cache manifest
|
|
118
|
+
(0, cache_1.writeCacheManifest)(animations, publicDir);
|
|
119
|
+
// Clean orphans
|
|
120
|
+
if (config.cleanOrphans !== false) {
|
|
121
|
+
(0, cache_1.cleanOrphanedCache)(animations, publicDir);
|
|
122
|
+
}
|
|
123
|
+
console.log("[manim-scroll] Build complete.");
|
|
124
|
+
}
|
|
125
|
+
main().catch((error) => {
|
|
126
|
+
console.error("[manim-scroll] Fatal error:", error);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ExtractedAnimation } from "./extractor";
|
|
2
|
+
export interface RenderOptions {
|
|
3
|
+
/** Path to the Python executable */
|
|
4
|
+
pythonPath?: string;
|
|
5
|
+
/** Path to the render CLI script */
|
|
6
|
+
cliPath?: string;
|
|
7
|
+
/** Path to the scene templates directory */
|
|
8
|
+
templatesDir?: string;
|
|
9
|
+
/** Maximum number of parallel renders */
|
|
10
|
+
concurrency?: number;
|
|
11
|
+
/** FPS for the animation */
|
|
12
|
+
fps?: number;
|
|
13
|
+
/** Resolution in "WIDTHxHEIGHT" format */
|
|
14
|
+
resolution?: string;
|
|
15
|
+
/** Manim quality preset (l, m, h, k) */
|
|
16
|
+
quality?: string;
|
|
17
|
+
/** Output format (frames, video, both) */
|
|
18
|
+
format?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface RenderResult {
|
|
21
|
+
animation: ExtractedAnimation;
|
|
22
|
+
hash: string;
|
|
23
|
+
success: boolean;
|
|
24
|
+
manifestUrl: string;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Render multiple animations in parallel with a concurrency limit.
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderAnimations(animations: ExtractedAnimation[], publicDir: string, options?: RenderOptions): Promise<RenderResult[]>;
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.renderAnimations = renderAnimations;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const cache_1 = require("./cache");
|
|
42
|
+
const DEFAULT_OPTIONS = {
|
|
43
|
+
pythonPath: "python3",
|
|
44
|
+
concurrency: Math.max(1, os.cpus().length - 1),
|
|
45
|
+
fps: 30,
|
|
46
|
+
resolution: "1920x1080",
|
|
47
|
+
quality: "h",
|
|
48
|
+
format: "both",
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Find the render CLI path relative to this package.
|
|
52
|
+
*/
|
|
53
|
+
function findCliPath(providedPath) {
|
|
54
|
+
if (providedPath) {
|
|
55
|
+
return providedPath;
|
|
56
|
+
}
|
|
57
|
+
// Try to find the CLI relative to the package
|
|
58
|
+
const candidates = [
|
|
59
|
+
path.resolve(__dirname, "../../render/cli.py"),
|
|
60
|
+
path.resolve(__dirname, "../../../render/cli.py"),
|
|
61
|
+
path.resolve(process.cwd(), "render/cli.py"),
|
|
62
|
+
path.resolve(process.cwd(), "node_modules/@mihirsarya/manim-scroll-next/render/cli.py"),
|
|
63
|
+
];
|
|
64
|
+
for (const candidate of candidates) {
|
|
65
|
+
if (fs.existsSync(candidate)) {
|
|
66
|
+
return candidate;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
throw new Error("Could not find render/cli.py. Please provide the cliPath option or ensure the render directory is accessible.");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Find the templates directory.
|
|
73
|
+
*/
|
|
74
|
+
function findTemplatesDir(providedPath) {
|
|
75
|
+
if (providedPath) {
|
|
76
|
+
return providedPath;
|
|
77
|
+
}
|
|
78
|
+
const candidates = [
|
|
79
|
+
path.resolve(__dirname, "../../render/templates"),
|
|
80
|
+
path.resolve(__dirname, "../../../render/templates"),
|
|
81
|
+
path.resolve(process.cwd(), "render/templates"),
|
|
82
|
+
path.resolve(process.cwd(), "node_modules/@mihirsarya/manim-scroll-next/render/templates"),
|
|
83
|
+
];
|
|
84
|
+
for (const candidate of candidates) {
|
|
85
|
+
if (fs.existsSync(candidate)) {
|
|
86
|
+
return candidate;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw new Error("Could not find render/templates directory. Please provide the templatesDir option.");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Render a single animation using the Python CLI.
|
|
93
|
+
*/
|
|
94
|
+
async function renderAnimation(animation, publicDir, options) {
|
|
95
|
+
var _a, _b, _c, _d, _e;
|
|
96
|
+
const hash = (0, cache_1.computePropsHash)(animation.scene, animation.props);
|
|
97
|
+
const outputDir = (0, cache_1.getOutputDir)(hash, publicDir);
|
|
98
|
+
const manifestUrl = `/manim-assets/${hash}/manifest.json`;
|
|
99
|
+
const pythonPath = (_a = options.pythonPath) !== null && _a !== void 0 ? _a : DEFAULT_OPTIONS.pythonPath;
|
|
100
|
+
const cliPath = findCliPath(options.cliPath);
|
|
101
|
+
const templatesDir = findTemplatesDir(options.templatesDir);
|
|
102
|
+
// Ensure output directory exists
|
|
103
|
+
(0, cache_1.ensureAssetDir)(publicDir);
|
|
104
|
+
// Create a temp props file
|
|
105
|
+
const propsFile = path.join(os.tmpdir(), `manim-props-${hash}.json`);
|
|
106
|
+
fs.writeFileSync(propsFile, JSON.stringify(animation.props, null, 2));
|
|
107
|
+
// Determine scene file path
|
|
108
|
+
const sceneFile = path.join(templatesDir, `${animation.scene.toLowerCase().replace("scene", "_scene")}.py`);
|
|
109
|
+
const actualSceneFile = fs.existsSync(sceneFile)
|
|
110
|
+
? sceneFile
|
|
111
|
+
: path.join(templatesDir, "text_scene.py");
|
|
112
|
+
const args = [
|
|
113
|
+
cliPath,
|
|
114
|
+
"--scene-file",
|
|
115
|
+
actualSceneFile,
|
|
116
|
+
"--scene-name",
|
|
117
|
+
animation.scene,
|
|
118
|
+
"--output-dir",
|
|
119
|
+
outputDir,
|
|
120
|
+
"--format",
|
|
121
|
+
(_b = options.format) !== null && _b !== void 0 ? _b : DEFAULT_OPTIONS.format,
|
|
122
|
+
"--fps",
|
|
123
|
+
String((_c = options.fps) !== null && _c !== void 0 ? _c : DEFAULT_OPTIONS.fps),
|
|
124
|
+
"--resolution",
|
|
125
|
+
(_d = options.resolution) !== null && _d !== void 0 ? _d : DEFAULT_OPTIONS.resolution,
|
|
126
|
+
"--quality",
|
|
127
|
+
(_e = options.quality) !== null && _e !== void 0 ? _e : DEFAULT_OPTIONS.quality,
|
|
128
|
+
"--props",
|
|
129
|
+
propsFile,
|
|
130
|
+
];
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
const process = (0, child_process_1.spawn)(pythonPath, args, {
|
|
133
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
134
|
+
env: {
|
|
135
|
+
...global.process.env,
|
|
136
|
+
MANIM_SCROLL_PROPS: propsFile,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
let stdout = "";
|
|
140
|
+
let stderr = "";
|
|
141
|
+
process.stdout.on("data", (data) => {
|
|
142
|
+
stdout += data.toString();
|
|
143
|
+
});
|
|
144
|
+
process.stderr.on("data", (data) => {
|
|
145
|
+
stderr += data.toString();
|
|
146
|
+
});
|
|
147
|
+
process.on("close", (code) => {
|
|
148
|
+
// Clean up temp props file
|
|
149
|
+
try {
|
|
150
|
+
fs.unlinkSync(propsFile);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Ignore cleanup errors
|
|
154
|
+
}
|
|
155
|
+
if (code === 0) {
|
|
156
|
+
resolve({
|
|
157
|
+
animation,
|
|
158
|
+
hash,
|
|
159
|
+
success: true,
|
|
160
|
+
manifestUrl,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
resolve({
|
|
165
|
+
animation,
|
|
166
|
+
hash,
|
|
167
|
+
success: false,
|
|
168
|
+
manifestUrl,
|
|
169
|
+
error: stderr || stdout || `Process exited with code ${code}`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
process.on("error", (err) => {
|
|
174
|
+
resolve({
|
|
175
|
+
animation,
|
|
176
|
+
hash,
|
|
177
|
+
success: false,
|
|
178
|
+
manifestUrl,
|
|
179
|
+
error: err.message,
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Run a limited number of promises concurrently.
|
|
186
|
+
*/
|
|
187
|
+
async function runWithConcurrency(items, concurrency, fn) {
|
|
188
|
+
const results = [];
|
|
189
|
+
const executing = [];
|
|
190
|
+
for (const item of items) {
|
|
191
|
+
const promise = fn(item).then((result) => {
|
|
192
|
+
results.push(result);
|
|
193
|
+
});
|
|
194
|
+
executing.push(promise);
|
|
195
|
+
if (executing.length >= concurrency) {
|
|
196
|
+
await Promise.race(executing);
|
|
197
|
+
// Remove completed promises
|
|
198
|
+
for (let i = executing.length - 1; i >= 0; i--) {
|
|
199
|
+
const p = executing[i];
|
|
200
|
+
// Check if promise is settled by racing with resolved promise
|
|
201
|
+
const settled = await Promise.race([
|
|
202
|
+
p.then(() => true).catch(() => true),
|
|
203
|
+
Promise.resolve(false),
|
|
204
|
+
]);
|
|
205
|
+
if (settled) {
|
|
206
|
+
executing.splice(i, 1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
await Promise.all(executing);
|
|
212
|
+
return results;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Render multiple animations in parallel with a concurrency limit.
|
|
216
|
+
*/
|
|
217
|
+
async function renderAnimations(animations, publicDir, options = {}) {
|
|
218
|
+
var _a;
|
|
219
|
+
if (animations.length === 0) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
const concurrency = (_a = options.concurrency) !== null && _a !== void 0 ? _a : DEFAULT_OPTIONS.concurrency;
|
|
223
|
+
console.log(`Rendering ${animations.length} animation(s) with concurrency ${concurrency}...`);
|
|
224
|
+
const results = await runWithConcurrency(animations, concurrency, (animation) => renderAnimation(animation, publicDir, options));
|
|
225
|
+
const successful = results.filter((r) => r.success).length;
|
|
226
|
+
const failed = results.filter((r) => !r.success);
|
|
227
|
+
console.log(`Rendered ${successful}/${animations.length} animation(s) successfully.`);
|
|
228
|
+
if (failed.length > 0) {
|
|
229
|
+
console.error("Failed to render the following animations:");
|
|
230
|
+
for (const result of failed) {
|
|
231
|
+
console.error(` - ${result.animation.id}: ${result.error}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return results;
|
|
235
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mihirsarya/manim-scroll-next",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Next.js plugin for build-time Manim scroll animation rendering.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"next": ">=13.0.0"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@babel/parser": "^7.24.0",
|
|
15
|
+
"@babel/traverse": "^7.24.0",
|
|
16
|
+
"@babel/types": "^7.24.0",
|
|
17
|
+
"glob": "^10.3.10"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/babel__traverse": "^7.20.5",
|
|
21
|
+
"@types/node": "^20.11.0",
|
|
22
|
+
"typescript": "^5.4.5"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.json"
|
|
26
|
+
}
|
|
27
|
+
}
|