mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-12 07:18:01 -05:00
feat: add rendering again (#43)
This commit is contained in:
42
package-lock.json
generated
42
package-lock.json
generated
@@ -6114,6 +6114,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-fccbsHKqFDXClBZTDLA43zl0+TbxyIwyzIzwwhvoJvhNjOErCdeX2xJbURimv2EbSVUGav001PaCJg4mZxMl4w=="
|
||||
},
|
||||
"node_modules/@types/mime-types": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz",
|
||||
"integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
|
||||
@@ -22388,7 +22394,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@motion-canvas/core",
|
||||
"version": "9.1.2",
|
||||
"version": "10.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prismjs": "^1.26.0",
|
||||
@@ -22417,7 +22423,7 @@
|
||||
},
|
||||
"packages/create": {
|
||||
"name": "@motion-canvas/create",
|
||||
"version": "9.1.2",
|
||||
"version": "10.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prompts": "^2.4.2"
|
||||
@@ -22426,9 +22432,9 @@
|
||||
"create-motion-canvas": "index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/ui": "*",
|
||||
"@motion-canvas/vite-plugin": "*"
|
||||
"@motion-canvas/core": "^10.0.0",
|
||||
"@motion-canvas/ui": "^10.0.0",
|
||||
"@motion-canvas/vite-plugin": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"packages/docs": {
|
||||
@@ -22470,10 +22476,10 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@motion-canvas/ui",
|
||||
"version": "9.1.2",
|
||||
"version": "10.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "^9.1.2",
|
||||
"@motion-canvas/core": "^10.0.0",
|
||||
"@preact/preset-vite": "^2.3.0",
|
||||
"preact": "10.7.3",
|
||||
"typescript": "^4.6.4",
|
||||
@@ -22495,9 +22501,13 @@
|
||||
},
|
||||
"packages/vite-plugin": {
|
||||
"name": "@motion-canvas/vite-plugin",
|
||||
"version": "9.1.1",
|
||||
"version": "10.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "^2.1.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.5"
|
||||
},
|
||||
@@ -26255,9 +26265,9 @@
|
||||
"@motion-canvas/create": {
|
||||
"version": "file:packages/create",
|
||||
"requires": {
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/ui": "*",
|
||||
"@motion-canvas/vite-plugin": "*",
|
||||
"@motion-canvas/core": "^10.0.0",
|
||||
"@motion-canvas/ui": "^10.0.0",
|
||||
"@motion-canvas/vite-plugin": "^10.0.0",
|
||||
"prompts": "^2.4.2"
|
||||
}
|
||||
},
|
||||
@@ -26291,7 +26301,7 @@
|
||||
"@motion-canvas/ui": {
|
||||
"version": "file:packages/ui",
|
||||
"requires": {
|
||||
"@motion-canvas/core": "^9.1.2",
|
||||
"@motion-canvas/core": "^10.0.0",
|
||||
"@preact/preset-vite": "^2.3.0",
|
||||
"preact": "10.7.3",
|
||||
"typescript": "^4.6.4",
|
||||
@@ -26309,6 +26319,8 @@
|
||||
"@motion-canvas/vite-plugin": {
|
||||
"version": "file:packages/vite-plugin",
|
||||
"requires": {
|
||||
"@types/mime-types": "*",
|
||||
"mime-types": "^2.1.35",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.5"
|
||||
}
|
||||
@@ -27236,6 +27248,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-fccbsHKqFDXClBZTDLA43zl0+TbxyIwyzIzwwhvoJvhNjOErCdeX2xJbURimv2EbSVUGav001PaCJg4mZxMl4w=="
|
||||
},
|
||||
"@types/mime-types": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz",
|
||||
"integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
|
||||
|
||||
@@ -3,6 +3,7 @@ import {Meta, Metadata} from './Meta';
|
||||
import {EventDispatcher, ValueDispatcher} from './events';
|
||||
import {Size, CanvasColorSpace} from './types';
|
||||
import {AudioManager} from './media';
|
||||
import {ifHot} from './utils';
|
||||
|
||||
export const ProjectSize = {
|
||||
FullHD: {width: 1920, height: 1080},
|
||||
@@ -115,6 +116,7 @@ export class Project {
|
||||
|
||||
public readonly name: string;
|
||||
public readonly audio = new AudioManager();
|
||||
private readonly renderLookup: Record<number, Callback> = {};
|
||||
private _resolutionScale = 1;
|
||||
private _colorSpace: CanvasColorSpace = 'srgb';
|
||||
private _speed = 1;
|
||||
@@ -160,6 +162,12 @@ export class Project {
|
||||
instance.onReloaded.subscribe(() => this.reloaded.dispatch());
|
||||
this.sceneLookup[scene.name] = instance;
|
||||
}
|
||||
|
||||
ifHot(hot => {
|
||||
hot.on('motion-canvas:export-ack', ({frame}) => {
|
||||
this.renderLookup[frame]?.();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public transformCanvas(context: CanvasRenderingContext2D) {
|
||||
@@ -286,6 +294,34 @@ export class Project {
|
||||
return finished;
|
||||
}
|
||||
|
||||
public async export() {
|
||||
const frame = this.frame;
|
||||
|
||||
if (this.renderLookup[frame]) {
|
||||
console.warn(`Frame no. ${frame} is already being exported`);
|
||||
return;
|
||||
}
|
||||
|
||||
await ifHot(
|
||||
hot =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
delete this.renderLookup[frame];
|
||||
reject(`Connection timeout when exporting frame no. ${frame}`);
|
||||
}, 1000);
|
||||
this.renderLookup[frame] = () => {
|
||||
delete this.renderLookup[frame];
|
||||
resolve();
|
||||
};
|
||||
hot.send('motion-canvas:export', {
|
||||
frame,
|
||||
data: this.canvas.toDataURL('image/png'),
|
||||
mimeType: 'image/png',
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public syncAudio(frameOffset = 0) {
|
||||
this.audio.setTime(this.framesToSeconds(this.frame + frameOffset));
|
||||
}
|
||||
@@ -333,10 +369,4 @@ export class Project {
|
||||
public framesToSeconds(frames: number) {
|
||||
return frames / this.framesPerSeconds;
|
||||
}
|
||||
|
||||
public async getBlob(): Promise<Blob> {
|
||||
return new Promise<Blob>(resolve =>
|
||||
this.canvas.toBlob(resolve, 'image/png'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type {Project} from '../Project';
|
||||
import {
|
||||
AsyncEventDispatcher,
|
||||
EventDispatcher,
|
||||
ValueDispatcher,
|
||||
} from '../events';
|
||||
import {EventDispatcher, ValueDispatcher} from '../events';
|
||||
import type {CanvasColorSpace} from '../types';
|
||||
|
||||
const MAX_AUDIO_DESYNC = 1 / 50;
|
||||
@@ -29,11 +25,6 @@ interface PlayerCommands {
|
||||
recalculate: boolean;
|
||||
}
|
||||
|
||||
export interface RenderData {
|
||||
frame: number;
|
||||
data: Blob;
|
||||
}
|
||||
|
||||
export class Player {
|
||||
public get onStateChanged() {
|
||||
return this.state.subscribable;
|
||||
@@ -59,11 +50,6 @@ export class Player {
|
||||
}
|
||||
private readonly frame = new ValueDispatcher(0);
|
||||
|
||||
public get onFrameRendered() {
|
||||
return this.frameRendered.subscribable;
|
||||
}
|
||||
private readonly frameRendered = new AsyncEventDispatcher<RenderData>();
|
||||
|
||||
public get onReloaded() {
|
||||
return this.reloaded.subscribable;
|
||||
}
|
||||
@@ -270,10 +256,13 @@ export class Player {
|
||||
if (state.render) {
|
||||
state.finished = await this.project.next();
|
||||
this.project.render();
|
||||
await this.frameRendered.dispatch({
|
||||
frame: this.project.frame,
|
||||
data: await this.project.getBlob(),
|
||||
});
|
||||
try {
|
||||
await this.project.export();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.toggleRendering(false);
|
||||
}
|
||||
|
||||
if (state.finished || this.project.frame >= state.endFrame) {
|
||||
this.toggleRendering(false);
|
||||
}
|
||||
|
||||
2
packages/core/src/vite-env.d.ts
vendored
2
packages/core/src/vite-env.d.ts
vendored
@@ -6,6 +6,8 @@ declare module 'vite/types/customEvent' {
|
||||
interface CustomEventMap {
|
||||
'motion-canvas:meta': {source: string; data: import('./Meta').Metadata};
|
||||
'motion-canvas:meta-ack': {source: string};
|
||||
'motion-canvas:export': {frame: number; data: string; mimeType: string};
|
||||
'motion-canvas:export-ack': {frame: number};
|
||||
'motion-canvas:assets': {urls: string[]};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {usePlayerState, useSubscribable} from '../../hooks';
|
||||
import {usePlayerState} from '../../hooks';
|
||||
import {Button, Group, Input, Label, Select} from '../controls';
|
||||
import {Pane} from '../tabs';
|
||||
import {usePlayer} from '../../contexts';
|
||||
@@ -20,23 +20,6 @@ export function Rendering() {
|
||||
{value: 'display-p3', text: 'DCI-P3'},
|
||||
];
|
||||
|
||||
useSubscribable(
|
||||
player.onFrameRendered,
|
||||
async ({frame, data}) => {
|
||||
try {
|
||||
const name = frame.toString().padStart(6, '0');
|
||||
await fetch(`/render/frame${name}.png`, {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
player.toggleRendering(false);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Pane title="Rendering">
|
||||
<Group>
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
"vite": "3.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime-types": "^2.1.35"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {Plugin, ResolvedConfig} from 'vite';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import {Readable} from 'stream';
|
||||
import mime from 'mime-types';
|
||||
|
||||
export interface MotionCanvasPluginConfig {
|
||||
/**
|
||||
@@ -14,6 +15,12 @@ export interface MotionCanvasPluginConfig {
|
||||
* @default './src/project.ts'
|
||||
*/
|
||||
project?: string;
|
||||
/**
|
||||
* A directory path to which the animation will be rendered.
|
||||
*
|
||||
* @default './output'
|
||||
*/
|
||||
output?: string;
|
||||
/**
|
||||
* Defines which assets should be buffered before being sent to the browser.
|
||||
*
|
||||
@@ -53,6 +60,7 @@ export interface MotionCanvasPluginConfig {
|
||||
|
||||
export default ({
|
||||
project = './src/project.ts',
|
||||
output = './output',
|
||||
bufferedAssets = /\.(wav|mp3|ogg)$/,
|
||||
editor: {
|
||||
styles = '@motion-canvas/ui/dist/style.css',
|
||||
@@ -64,6 +72,7 @@ export default ({
|
||||
const resolvedEditorId = '\0' + editorId;
|
||||
const timeStamps: Record<string, number> = {};
|
||||
const projectName = path.parse(project).name;
|
||||
const outputPath = path.resolve(output);
|
||||
|
||||
let viteConfig: ResolvedConfig;
|
||||
|
||||
@@ -237,6 +246,25 @@ export default ({
|
||||
);
|
||||
client.send('motion-canvas:meta-ack', {source});
|
||||
});
|
||||
server.ws.on(
|
||||
'motion-canvas:export',
|
||||
async ({frame, mimeType, data}, client) => {
|
||||
const name = frame.toString().padStart(6, '0');
|
||||
const extension = mime.extension(mimeType);
|
||||
const file = path.join(outputPath, name + '.' + extension);
|
||||
|
||||
const directory = path.dirname(file);
|
||||
if (!fs.existsSync(directory)) {
|
||||
fs.mkdirSync(directory, {recursive: true});
|
||||
}
|
||||
|
||||
const base64Data = data.slice(data.indexOf(',') + 1);
|
||||
await fs.promises.writeFile(file, base64Data, {
|
||||
encoding: 'base64',
|
||||
});
|
||||
client.send('motion-canvas:export-ack', {frame});
|
||||
},
|
||||
);
|
||||
},
|
||||
config() {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user