Skip to content

Commit

Permalink
headless rendering (#4)
Browse files Browse the repository at this point in the history
This PR introduces the renderer package, which makes it possible to render videos via a function call (using a headless browser).
  • Loading branch information
justusmattern27 authored Mar 14, 2024
1 parent d0f72b6 commit 724582b
Show file tree
Hide file tree
Showing 24 changed files with 15,581 additions and 32,592 deletions.
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
'examples',
'legacy',
'player',
'renderer',
'ui',
'vite-plugin',
],
Expand Down
47,949 changes: 15,384 additions & 32,565 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"devDependencies": {
"@commitlint/cli": "^18.4.2",
"@commitlint/config-conventional": "^18.4.2",
"@types/node": "~18.14.0",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
Expand All @@ -49,13 +50,15 @@
"prettier": "^3.1.0",
"prettier-plugin-organize-imports": "^3.2.4",
"typescript": "^5.2.2",
"vite": "^4.5.0"
"vite": "^4.5.2"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{js,jsx,ts,tsx,md,scss}": "prettier --write"
},
"dependencies": {
"@wooorm/starry-night": "^3.3.0",
"shiki": "^1.1.7",
"uuid": "^9.0.1"
}
}
13 changes: 6 additions & 7 deletions packages/2d/src/editor/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,12 @@ export function Provider({children}: {children?: ComponentChildren}) {

state.scene.value = currentScene as Scene2D;

useSignalEffect(
() =>
state.scene.value?.onRenderLifecycle.subscribe(([event]) => {
if (event === SceneRenderEvent.AfterRender) {
state.afterRender.value++;
}
}),
useSignalEffect(() =>
state.scene.value?.onRenderLifecycle.subscribe(([event]) => {
if (event === SceneRenderEvent.AfterRender) {
state.afterRender.value++;
}
}),
);

useSignalEffect(() => {
Expand Down
9 changes: 5 additions & 4 deletions packages/2d/src/lib/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ export type ComponentChild =
export type ComponentChildren = ComponentChild | ComponentChild[];
export type NodeChildren = Node | Node[];

export type PropsOf<T> = T extends NodeConstructor<infer P>
? P
: T extends FunctionComponent<infer P>
export type PropsOf<T> =
T extends NodeConstructor<infer P>
? P
: never;
: T extends FunctionComponent<infer P>
? P
: never;

export interface JSXProps {
children?: ComponentChildren;
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/scenes/Scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,8 @@ export interface SceneDescriptionReload<T = unknown> {
stack?: string;
}

export type DescriptionOf<TScene> = TScene extends Scene<infer TConfig>
? SceneDescription<TConfig>
: never;
export type DescriptionOf<TScene> =
TScene extends Scene<infer TConfig> ? SceneDescription<TConfig> : never;

/**
* Describes cached information about the timing of a scene.
Expand Down
2 changes: 1 addition & 1 deletion packages/create/template-2d-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
"@motion-canvas/ui": "*",
"@motion-canvas/vite-plugin": "*",
"typescript": "^5.2.2",
"vite": "^4.0.0"
"vite": "^5.0"
}
}
2 changes: 1 addition & 1 deletion packages/ffmpeg/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@
"fluent-ffmpeg": "^2.1.2",
"@motion-canvas/core": "^3.14.1",
"@motion-canvas/vite-plugin": "^3.14.1",
"vite": "4.x"
"vite": "^4.5"
}
}
1 change: 1 addition & 0 deletions packages/renderer/client/motion-canvas.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@motion-canvas/core/project" />
20 changes: 20 additions & 0 deletions packages/renderer/client/render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type {Project} from '@motion-canvas/core';
import {Renderer} from '@motion-canvas/core';

declare global {
interface Window {
onRenderComplete: () => void;
}
}

export const render = async (project: Project) => {
try {
const renderer = new Renderer(project);
await renderer.render({
...project.meta.getFullRenderingSettings(),
name: project.name,
});
} finally {
window.onRenderComplete();
}
};
14 changes: 14 additions & 0 deletions packages/renderer/client/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"baseUrl": "src",
"outDir": "../dist/client",
"strict": true,
"module": "es2020",
"target": "es2020",
"moduleResolution": "node",
"declaration": true,
"inlineSourceMap": true,
"allowSyntheticDefaultImports": true
},
"include": ["*"]
}
25 changes: 25 additions & 0 deletions packages/renderer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@motion-canvas/renderer",
"version": "3.14.1",
"description": "A headless renderer for Motion Canvas",
"main": "dist/index.js",
"author": "motion-canvas",
"homepage": "https://motioncanvas.io/",
"bugs": "https://github.com/motion-canvas/motion-canvas/issues",
"license": "MIT",
"scripts": {
"build": "npm run client:build && npm run server:build",
"client:build": "tsc --project client/tsconfig.json",
"client:dev": "tsc -w --project client/tsconfig.json",
"server:build": "tsc --project server/tsconfig.json",
"server:dev": "tsc -w --project server/tsconfig.json"
},
"files": [
"dist",
"types"
],
"devDependencies": {
"@motion-canvas/core": "*",
"puppeteer": "^22.3.0"
}
}
12 changes: 12 additions & 0 deletions packages/renderer/renderer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Motion Canvas Renderer</title>
</head>
<body>
<h1>RENDERING!</h1>
<script type="module" src="{{source}}"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions packages/renderer/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './main';
export * from './plugin';
35 changes: 35 additions & 0 deletions packages/renderer/server/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as path from 'path';
import puppeteer from 'puppeteer';
import {createServer} from 'vite';

export const renderVideo = async (configFile: string) => {
console.log('Rendering...');

const resolvedConfigPath = path.resolve(process.cwd(), configFile);

const [browser, server] = await Promise.all([
puppeteer.launch({headless: true}),
createServer({
configFile: resolvedConfigPath,
server: {
port: 9000,
},
}).then(server => server.listen()),
]);

const page = await browser.newPage();
if (!server.httpServer) {
throw new Error('HTTP server is not initialized');
}
const address = server.httpServer.address();
const port = address && typeof address === 'object' ? address.port : null;
if (port === null) {
throw new Error('Server address is null');
}

await page.goto(`http://localhost:${port}/render`);
await page.exposeFunction('onRenderComplete', async () => {
await Promise.all([browser.close(), server.close()]);
console.log('Rendering complete.');
});
};
1 change: 1 addition & 0 deletions packages/renderer/server/motion-canvas.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@motion-canvas/core/project" />
34 changes: 34 additions & 0 deletions packages/renderer/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as fs from 'fs';
import * as path from 'path';
import {Plugin} from 'vite';

const RendererPath = path.resolve(__dirname, '../renderer.html');
const Content = fs.readFileSync(RendererPath, 'utf-8');
const HtmlParts = Content.toString().split('{{source}}');

function createHtml(src: string) {
return HtmlParts[0] + src + HtmlParts[1];
}

export function rendererPlugin(): Plugin {
return {
name: 'motion-canvas-renderer-plugin',

async load(id) {
if (id.startsWith('\x00virtual:renderer')) {
return `\
import {render} from '@motion-canvas/renderer/client/render';
import project from './src/project.ts?project';
render(project);
`;
}
},

configureServer(server) {
server.middlewares.use('/render', (_req, res) => {
res.setHeader('Content-Type', 'text/html');
res.end(createHtml(`/@id/__x00__virtual:renderer`));
});
},
};
}
14 changes: 14 additions & 0 deletions packages/renderer/server/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"baseUrl": "src",
"outDir": "../dist",
"strict": true,
"module": "CommonJS",
"target": "es2020",
"moduleResolution": "node",
"declaration": true,
"inlineSourceMap": true,
"allowSyntheticDefaultImports": true
},
"include": ["*", "../index.ts", "../main.ts", "../motion-canvas.d.ts"]
}
4 changes: 3 additions & 1 deletion packages/template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
"main": "src/project.ts",
"scripts": {
"dev": "vite",
"build": "tsc && vite build"
"build": "tsc && vite build",
"render": "tsc && node dist/renderScript.mjs"
},
"dependencies": {
"@motion-canvas/core": "*",
"@motion-canvas/2d": "*",
"@motion-canvas/renderer": "*",
"@motion-canvas/ffmpeg": "*"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions packages/template/src/renderScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {renderVideo} from '@motion-canvas/renderer';

renderVideo('./vite.config.ts');
5 changes: 4 additions & 1 deletion packages/template/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"extends": "@motion-canvas/2d/tsconfig.project.json",
"compilerOptions": {
"types": ["node"]
"types": ["node"],
"noEmit": false,
"outDir": "dist",
"module": "CommonJS"
},
"include": ["src"]
}
2 changes: 2 additions & 0 deletions packages/template/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ffmpeg from '@motion-canvas/ffmpeg';
import markdown from '@motion-canvas/internal/vite/markdown-literals';
import {rendererPlugin} from '@motion-canvas/renderer';
import preact from '@preact/preset-vite';
import {defineConfig} from 'vite';
import motionCanvas from '../vite-plugin/src/main';
Expand All @@ -24,6 +25,7 @@ export default defineConfig({
},
plugins: [
markdown(),
rendererPlugin(),
ffmpeg(),
preact({
include: [
Expand Down
4 changes: 2 additions & 2 deletions packages/vite-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
"lib"
],
"peerDependencies": {
"vite": "4.x || 5.x"
"vite": "^4.5"
},
"devDependencies": {
"@types/follow-redirects": "^1.14.1",
"@types/mime-types": "^2.1.1",
"@types/node": "^18.14.0"
"@types/node": "~18.14.0"
},
"dependencies": {
"fast-glob": "^3.3.1",
Expand Down
11 changes: 5 additions & 6 deletions packages/vite-plugin/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,11 @@ export default ({
.map(plugin => plugin[PLUGIN_OPTIONS]),
);
await Promise.all(
plugins.map(
plugin =>
plugin.config?.({
output: outputPath,
projects: projects.list,
}),
plugins.map(plugin =>
plugin.config?.({
output: outputPath,
projects: projects.list,
}),
),
);
},
Expand Down

0 comments on commit 724582b

Please sign in to comment.