@omnimedia/omnitool
Version:
open source video processing tools
353 lines • 11.9 kB
JavaScript
import { hex } from "@e280/stz";
import { filters } from "../parts/filters.js";
import { Kind } from "../parts/item.js";
import { animationPresets, makeAnimationPresets, visualAnimations } from "../parts/animations/registry.js";
import { transitions } from "../parts/transitions.js";
import { captionDuration, captionPresets } from "../parts/captions.js";
export class O {
state;
constructor(state) {
this.state = state;
}
require(id) {
if (id === undefined)
return undefined;
const item = this.state.timeline.items.find(item => item.id === id);
return item;
}
get timeline() {
return this.state.timeline;
}
getId() {
return hex.toInteger(hex.random());
}
#mutate(fn) {
this.state.timeline = fn(this.state.timeline);
}
register(item) {
this.#mutate(state => ({
...state,
items: [...state.items, item]
}));
}
textStyle = (style) => {
const item = {
id: this.getId(),
kind: Kind.TextStyle,
style,
};
this.register(item);
return item;
};
spatial = (transform, crop) => {
const item = {
id: this.getId(),
kind: Kind.Spatial,
transform: transform ?? this.transform(),
crop,
};
this.register(item);
return item;
};
#registerAnimation = (anims) => {
const item = {
id: this.getId(),
kind: Kind.Animation,
anims,
};
this.register(item);
return item;
};
#attachAnimation = (item, animation) => {
const next = {
...item,
animationIds: [...(item.animationIds ?? []), animation.id]
};
this.set(item.id, next);
return next;
};
anim = {
scalar: (terp, track) => ({ terp, track }),
vec2: (terp, source) => {
const track = { x: [], y: [] };
for (const [time, [x, y]] of source) {
track.x.push([time, x]);
track.y.push([time, y]);
}
return { terp, track };
},
transform: (terp, source) => {
const track = {
position: { x: [], y: [] },
scale: { x: [], y: [] },
rotation: [],
};
for (const [time, [position, scale, rotation]] of source) {
track.position.x.push([time, position[0]]);
track.position.y.push([time, position[1]]);
track.scale.x.push([time, scale[0]]);
track.scale.y.push([time, scale[1]]);
track.rotation.push([time, rotation]);
}
return { terp, track };
},
};
#animationPresets = makeAnimationPresets((terp, track) => ({ terp, track }), (terp, source) => this.anim.transform(terp, source), options => this.transform(options));
#makeFilter = (type) => {
const make = (params) => {
const item = {
id: this.getId(),
kind: Kind.Filter,
type,
params,
};
this.register(item);
return item;
};
const action = ((item, params) => {
const filter = make(params);
const next = {
...item,
filterIds: [...(item.filterIds ?? []), filter.id]
};
this.set(item.id, next);
return next;
});
action.make = make;
return action;
};
#makeFilters = () => {
const entries = Object.entries(filters)
.map(([name, filter]) => [name, this.#makeFilter(filter.type)]);
return Object.fromEntries(entries);
};
filter = this.#makeFilters();
#makeAnimationValue = (key, terp, track) => (key === "transform"
? this.anim.transform(terp, track)
: this.anim.scalar(terp, track));
#makeAnimate = (key) => {
const make = (terp, track) => this.#registerAnimation({
[key]: this.#makeAnimationValue(key, terp, track)
});
const action = ((item, terp, track) => {
const animation = make(terp, track);
return this.#attachAnimation(item, animation);
});
action.make = make;
return action;
};
#makePresetAnimate = (key) => {
const make = (options) => {
const preset = animationPresets[key];
const anim = this.#animationPresets[key](options);
return this.#registerAnimation(preset.type === "motion"
? { transform: anim }
: { opacity: anim });
};
const action = ((item, options) => {
const animation = make(options);
return this.#attachAnimation(item, animation);
});
action.make = make;
return action;
};
#makeAnimateActions = () => {
const entries = Object.keys(visualAnimations)
.map(key => [key, this.#makeAnimate(key)]);
return Object.fromEntries(entries);
};
#makePresetAnimateActions = () => {
const entries = Object.keys(animationPresets)
.map(key => [key, this.#makePresetAnimate(key)]);
return Object.fromEntries(entries);
};
animate = {
...this.#makeAnimateActions(),
presets: this.#makePresetAnimateActions(),
};
sequence = (...input) => {
const [first, ...rest] = input;
const label = typeof first === "string" ? first : undefined;
const items = (label ? rest : input);
const item = {
id: this.getId(),
kind: Kind.Sequence,
label,
childrenIds: items.map(item => item.id)
};
this.register(item);
return item;
};
stack = (...input) => {
const [first, ...rest] = input;
const label = typeof first === "string" ? first : undefined;
const items = (label ? rest : input);
const item = {
kind: Kind.Stack,
id: this.getId(),
label,
childrenIds: items.map(item => item.id)
};
this.register(item);
return item;
};
video = (media, options) => {
if (!media.hasVideo)
throw new Error(`Video clip error: media "${media.datafile.filename}" has no video track.`);
const item = {
kind: Kind.Video,
id: this.getId(),
label: options?.label,
enabled: options?.enabled,
mediaHash: media.datafile.checksum.hash,
start: options?.start ?? 0,
duration: options?.duration ?? media.duration
};
this.register(item);
return item;
};
image = (media, options) => {
if (!media.isImage)
throw new Error(`Image error: media "${media.datafile.filename}" is not an image.`);
const item = {
kind: Kind.Image,
id: this.getId(),
label: options?.label,
enabled: options?.enabled,
mediaHash: media.datafile.checksum.hash,
duration: options?.duration ?? 2000
};
this.register(item);
return item;
};
audio = (media, options) => {
if (!media.hasAudio)
throw new Error(`Audio clip error: media "${media.datafile.filename}" has no audio track.`);
const item = {
kind: Kind.Audio,
id: this.getId(),
label: options?.label,
enabled: options?.enabled,
mediaHash: media.datafile.checksum.hash,
start: options?.start ?? 0,
duration: options?.duration ?? media.duration,
gain: options?.gain ?? 1
};
this.register(item);
return item;
};
text = (content, options) => {
const item = {
id: this.getId(),
label: options?.label,
enabled: options?.enabled,
content,
kind: Kind.Text,
duration: options?.duration ?? 2000
};
if (options?.styles)
item.styleId = this.textStyle(options.styles).id;
this.register(item);
return item;
};
#makeCaption = (transcript, preset, options) => {
const start = options?.start ?? 0;
const duration = options?.duration ?? Math.max(0, captionDuration(transcript, options) - start);
const item = {
id: this.getId(),
kind: Kind.Caption,
label: options?.label,
enabled: options?.enabled,
transcript,
itemId: options?.itemId,
start,
duration,
maxChars: options?.maxChars,
maxDuration: options?.maxDuration,
maxSilence: options?.maxSilence,
};
item.styleId = this.textStyle(options?.styles ?? preset.styles).id;
item.spatialId = this.spatial(this.transform(preset.transform)).id;
this.register(item);
return item;
};
#makeCaptionAction = (preset) => {
const make = (transcript, options) => this.#makeCaption(transcript, preset, options);
const action = ((item, transcript, options) => {
const caption = make(transcript, {
...options,
itemId: item.id,
start: options?.start ?? item.start,
duration: options?.duration ?? item.duration,
});
return this.stack(caption, item);
});
action.make = make;
return action;
};
#makeCaptionPresetActions = () => {
const entries = Object.entries(captionPresets)
.map(([name, preset]) => [name, this.#makeCaptionAction(preset)]);
return Object.fromEntries(entries);
};
captions = Object.assign(this.#makeCaptionAction(captionPresets.default), { presets: this.#makeCaptionPresetActions() });
gap = (duration, options) => {
const item = {
id: this.getId(),
kind: Kind.Gap,
label: options?.label,
enabled: options?.enabled,
duration
};
this.register(item);
return item;
};
#makeTransition = (key) => {
return (duration, options) => {
const item = {
id: this.getId(),
kind: Kind.Transition,
label: options?.label,
enabled: options?.enabled,
name: transitions[key].name,
duration,
};
this.register(item);
return item;
};
};
#makeTransitions = () => {
const entries = Object.keys(transitions)
.map(key => [key, this.#makeTransition(key)]);
return Object.fromEntries(entries);
};
transition = this.#makeTransitions();
transform = (options) => {
const position = [
options?.position?.[0] ?? 0,
options?.position?.[1] ?? 0
];
const scale = [
options?.scale?.[0] ?? 1,
options?.scale?.[1] ?? 1
];
const rotation = options?.rotation ?? 0;
return [position, scale, rotation];
};
addChildren(parent, ...items) {
this.#mutate(state => {
const parentItem = state.items.find(({ id }) => id === parent.id);
parentItem.childrenIds.push(...items.map(item => item.id));
return state;
});
}
set = (id, partial) => {
this.#mutate(project => ({
...project,
items: project.items.map(item => item.id === id
? { ...item, ...partial }
: item)
}));
};
}
//# sourceMappingURL=o.js.map