123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
- import * as HME from 'h264-mp4-encoder';
- import { Viewport } from '../../mol-canvas3d/camera/util';
- import { ImagePass } from '../../mol-canvas3d/passes/image';
- import { PluginStateAnimation } from '../../mol-plugin-state/animation/model';
- import { PluginContext } from '../../mol-plugin/context';
- import { RuntimeContext } from '../../mol-task';
- import { Color } from '../../mol-util/color';
- export interface Mp4EncoderParams<A extends PluginStateAnimation = PluginStateAnimation> {
- pass: ImagePass,
- customBackground?: Color,
- animation: PluginStateAnimation.Instance<A>,
- width: number,
- height: number,
- viewport: Viewport,
- /** default is 30 */
- fps?: number,
- /** Number from 10 (best quality, slowest) to 51 (worst, fastest) */
- quantizationParameter?: number
- }
- export async function encodeMp4Animation<A extends PluginStateAnimation>(plugin: PluginContext, ctx: RuntimeContext, params: Mp4EncoderParams<A>) {
- await ctx.update({ message: 'Initializing...', isIndeterminate: true });
- validateViewport(params);
- const durationMs = PluginStateAnimation.getDuration(plugin, params.animation);
- if (durationMs === void 0) {
- throw new Error('The animation does not have the duration specified.');
- }
- const encoder = await HME.createH264MP4Encoder();
- const { width, height } = params;
- let vw = params.viewport.width, vh = params.viewport.height;
- // dimensions must be a multiple of 2
- if (vw % 2 !== 0) vw -= 1;
- if (vh % 2 !== 0) vh -= 1;
- const normalizedViewport: Viewport = { ...params.viewport, width: vw, height: vh };
- encoder.width = vw;
- encoder.height = vh;
- if (params.quantizationParameter) encoder.quantizationParameter = params.quantizationParameter;
- if (params.fps) encoder.frameRate = params.fps;
- encoder.initialize();
- const loop = plugin.animationLoop;
- const canvasProps = plugin.canvas3d?.props;
- const wasAnimating = loop.isAnimating;
- let stoppedAnimation = true, finalized = false;
- try {
- loop.stop();
- loop.resetTime(0);
- if (params.customBackground !== void 0) {
- plugin.canvas3d?.setProps({ renderer: { backgroundColor: params.customBackground }, transparentBackground: false }, true);
- }
- const fps = encoder.frameRate;
- const N = Math.ceil(durationMs / 1000 * fps);
- const dt = durationMs / N;
- await ctx.update({ message: 'Rendering...', isIndeterminate: false, current: 0, max: N + 1 });
- await params.pass.updateBackground();
- await plugin.managers.animation.play(params.animation.definition, params.animation.params);
- stoppedAnimation = false;
- for (let i = 0; i <= N; i++) {
- await loop.tick(i * dt, { isSynchronous: true, animation: { currentFrame: i, frameCount: N }, manualDraw: true });
- const image = params.pass.getImageData(width, height, normalizedViewport);
- encoder.addFrameRgba(image.data);
- if (ctx.shouldUpdate) {
- await ctx.update({ current: i + 1 });
- }
- }
- await ctx.update({ message: 'Applying finishing touches...', isIndeterminate: true });
- await plugin.managers.animation.stop();
- stoppedAnimation = true;
- encoder.finalize();
- finalized = true;
- return encoder.FS.readFile(encoder.outputFilename);
- } finally {
- if (finalized) encoder.delete();
- if (params.customBackground !== void 0) {
- plugin.canvas3d?.setProps({ renderer: { backgroundColor: canvasProps?.renderer!.backgroundColor }, transparentBackground: canvasProps?.transparentBackground });
- }
- if (!stoppedAnimation) await plugin.managers.animation.stop();
- if (wasAnimating) loop.start();
- }
- }
- function validateViewport(params: Mp4EncoderParams) {
- if (params.viewport.x + params.viewport.width > params.width || params.viewport.y + params.viewport.height > params.height) {
- throw new Error('Viewport exceeds the canvas dimensions.');
- }
- }
|