feat: add rendering again (#43)

This commit is contained in:
Jacob
2022-08-15 22:24:55 +02:00
committed by GitHub
parent bbb5378e4c
commit c10d3dbb63
7 changed files with 109 additions and 55 deletions

42
package-lock.json generated
View File

@@ -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",

View File

@@ -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'),
);
}
}

View File

@@ -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);
}

View File

@@ -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[]};
}
}

View File

@@ -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>

View File

@@ -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"
}
}

View File

@@ -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 {