123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
- import { debounceTime } from 'rxjs/operators';
- import { PluginStateAnimation } from '../../mol-plugin-state/animation/model';
- import { PluginComponent } from '../../mol-plugin-state/component';
- import { PluginContext } from '../../mol-plugin/context';
- import { Task } from '../../mol-task';
- import { ParamDefinition as PD } from '../../mol-util/param-definition';
- import { encodeMp4Animation } from './encoder';
- export interface Mp4AnimationInfo {
- width: number,
- height: number
- }
- export const Mp4AnimationParams = {
- quantization: PD.Numeric(18, { min: 10, max: 51 }, { description: 'Lower is better, but slower.' })
- };
- export class Mp4Controls extends PluginComponent {
- private currentNames = new Set<string>();
- private animations: PluginStateAnimation[] = [];
- readonly behaviors = {
- animations: this.ev.behavior<PD.Params>({ }),
- current: this.ev.behavior<{ anim: PluginStateAnimation, params: PD.Params, values: any } | undefined>(void 0),
- canApply: this.ev.behavior<PluginStateAnimation.CanApply>({ canApply: false }),
- info: this.ev.behavior<Mp4AnimationInfo>({ width: 0, height: 0 }),
- params: this.ev.behavior<PD.Values<typeof Mp4AnimationParams>>(PD.getDefaultValues(Mp4AnimationParams))
- };
- setCurrent(name?: string) {
- const anim = this.animations.find(a => a.name === name);
- if (!anim) {
- this.behaviors.current.next(anim);
- return;
- }
- const params = anim.params(this.plugin) as PD.Params;
- const values = PD.getDefaultValues(params);
- this.behaviors.current.next({ anim, params, values });
- this.behaviors.canApply.next(anim.canApply?.(this.plugin) ?? { canApply: true });
- }
- setCurrentParams(values: any) {
- this.behaviors.current.next({ ...this.behaviors.current.value!, values });
- }
- get current() {
- return this.behaviors.current.value;
- }
- render() {
- const task = Task.create('Export Animation', async ctx => {
- try {
- const resolution = this.plugin.helpers.viewportScreenshot?.getSizeAndViewport()!;
- const anim = this.current!;
- const movie = await encodeMp4Animation(this.plugin, ctx, {
- animation: {
- definition: anim.anim,
- params: anim.values,
- },
- ...resolution,
- quantizationParameter: this.behaviors.params.value.quantization,
- pass: this.plugin.helpers.viewportScreenshot?.imagePass!,
- });
- const filename = anim.anim.display.name.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
- return { movie, filename: `${this.plugin.helpers.viewportScreenshot?.getFilename('')}_${filename}.mp4` };
- } catch (e) {
- this.plugin.log.error('Error during animation export');
- throw e;
- }
- });
- return this.plugin.runTask(task, { useOverlay: true });
- }
- private get manager() {
- return this.plugin.managers.animation;
- }
- private syncInfo() {
- const helper = this.plugin.helpers.viewportScreenshot;
- const size = helper?.getSizeAndViewport();
- if (!size) return;
- this.behaviors.info.next({ width: size.viewport.width, height: size.viewport.height });
- }
- private sync() {
- const animations = this.manager.animations.filter(a => a.isExportable);
- const hasAll = animations.every(a => this.currentNames.has(a.name));
- if (hasAll && this.currentNames.size === animations.length) {
- return;
- }
- const params = {
- current: PD.Select(animations[0]?.name,
- animations.map(a => [a.name, a.display.name] as [string, string]),
- { label: 'Animation' })
- };
- const current = this.behaviors.current.value;
- const hasCurrent = !!animations.find(a => a.name === current?.anim.name);
- this.animations = animations;
- if (!hasCurrent) {
- this.setCurrent(animations[0]?.name);
- }
- this.behaviors.animations.next(params);
- }
- private init() {
- if (!this.plugin.canvas3d) return;
- this.subscribe(this.plugin.managers.animation.events.updated.pipe(debounceTime(16)), () => {
- this.sync();
- });
- this.subscribe(this.plugin.canvas3d.resized, () => this.syncInfo());
- this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo());
- this.subscribe(this.plugin.behaviors.state.isBusy, b => this.updateCanApply(b));
- this.subscribe(this.plugin.managers.snapshot.events.changed, b => this.updateCanApply(b));
- this.sync();
- this.syncInfo();
- }
- private updateCanApply(b?: any) {
- const anim = this.current;
- if (!b && anim) {
- this.behaviors.canApply.next(anim.anim.canApply?.(this.plugin) ?? { canApply: true });
- }
- }
- constructor(private plugin: PluginContext) {
- super();
- this.init();
- }
- }
|