@omnimedia/omnitool
Version:
open source video processing tools
263 lines • 10.1 kB
JavaScript
import { ms } from '../../../units/ms.js';
import { I6, mul6, transformToMat6 } from '../../utils/matrix.js';
import { Kind } from '../../parts/item.js';
import { resolveScalarAnimation, resolveTransformAnimation } from '../../utils/anim.js';
function isPlayableItem(item) {
return 'duration' in item;
}
export function itemsAt(p) {
const results = [];
const itemMap = new Map(p.timeline.items.map(item => [item.id, item]));
walkAt(p.timeline.rootId, itemMap, p.timecode, {
sequence: () => { },
stack: () => { },
video: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
image: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
text: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
caption: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
audio: (item, localTime, ancestors) => results.push({ item, localTime, ancestors })
});
return results;
}
export function itemsFrom(p) {
const results = [];
const itemMap = new Map(p.timeline.items.map(item => [item.id, item]));
walkFrom(p.timeline.rootId, itemMap, p.from, {
sequence: () => { },
stack: () => { },
video: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
image: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
text: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
caption: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
audio: (item, localTime, ancestors) => results.push({ item, localTime, ancestors })
});
return results;
}
export function computeWorldMatrix(items, ancestors, item, localTime) {
let world = I6;
for (const ancestor of ancestors) {
world = applySpatialIfAny(items, ancestor.item, world, ancestor.localTime);
}
return applySpatialIfAny(items, item, world, localTime);
}
function applySpatialIfAny(items, item, parentMatrix, time) {
let matrix = parentMatrix;
if ("spatialId" in item && item.spatialId) {
const spatial = items.get(item.spatialId);
if (spatial && spatial.enabled !== false) {
const local = transformToMat6(spatial.transform);
matrix = mul6(local, matrix);
}
}
if ("animationIds" in item && item.animationIds) {
for (const id of item.animationIds) {
const animation = items.get(id);
const anim = animation?.anims.transform;
if (animation?.enabled !== false && anim && transformActiveAt(anim, time)) {
const local = transformToMat6(resolveTransformAnimation(time, anim));
matrix = mul6(local, matrix);
}
}
}
return matrix;
}
export function walk(id, items, parentMatrix, localTime, callbacks, ancestors = []) {
const item = items.get(id);
if (!item)
return;
const currentMatrix = applySpatialIfAny(items, item, parentMatrix, localTime);
switch (item.kind) {
case Kind.Stack:
callbacks.stack?.(item, currentMatrix, ancestors);
for (const childId of item.childrenIds) {
walk(childId, items, currentMatrix, localTime, callbacks, [...ancestors, { item, localTime }]);
}
break;
case Kind.Sequence: {
callbacks.sequence?.(item, currentMatrix, ancestors);
for (const childId of item.childrenIds) {
const child = items.get(childId);
if (!child)
continue;
if (!isPlayableItem(child)) {
continue;
}
walk(childId, items, currentMatrix, localTime, callbacks, [...ancestors, { item, localTime }]);
}
break;
}
case Kind.Video:
callbacks.video?.(item, currentMatrix, ancestors);
break;
case Kind.Image:
callbacks.image?.(item, currentMatrix, ancestors);
break;
case Kind.Text:
callbacks.text?.(item, currentMatrix, ancestors);
break;
case Kind.Caption:
callbacks.caption?.(item, currentMatrix, ancestors);
break;
case Kind.Audio:
callbacks.audio?.(item);
break;
}
}
function walkAt(id, items, time, callbacks, ancestors = []) {
const item = items.get(id);
if (!item)
return;
switch (item.kind) {
case Kind.Stack:
callbacks.stack(item, time, ancestors);
for (const childId of item.childrenIds) {
walkAt(childId, items, time, callbacks, [...ancestors, { item, localTime: time }]);
}
break;
case Kind.Sequence: {
callbacks.sequence(item, time, ancestors);
let offset = ms(0);
for (const childId of item.childrenIds) {
const child = items.get(childId);
if (!child)
continue;
const duration = computeItemDurationFromMap(child.id, items);
if (duration <= 0)
continue;
if (time >= offset && time < offset + duration) {
const localTime = ms(time - offset);
walkAt(childId, items, localTime, callbacks, [...ancestors, { item, localTime: time }]);
break;
}
offset = ms(offset + duration);
}
break;
}
case Kind.Video:
callbacks.video(item, time, ancestors);
break;
case Kind.Image:
callbacks.image(item, time, ancestors);
break;
case Kind.Text:
callbacks.text(item, time, ancestors);
break;
case Kind.Caption:
callbacks.caption(item, time, ancestors);
break;
case Kind.Audio:
callbacks.audio(item, time, ancestors);
break;
}
}
function walkFrom(id, items, from, callbacks, ancestors = []) {
const item = items.get(id);
if (!item)
return;
switch (item.kind) {
case Kind.Stack:
callbacks.stack(item, from, ancestors);
for (const childId of item.childrenIds) {
walkFrom(childId, items, from, callbacks, [...ancestors, { item, localTime: from }]);
}
break;
case Kind.Sequence: {
callbacks.sequence(item, from, ancestors);
let offset = ms(0);
for (const childId of item.childrenIds) {
const child = items.get(childId);
if (!child)
continue;
const duration = computeItemDurationFromMap(child.id, items);
if (duration <= 0)
continue;
const end = ms(offset + duration);
if (from >= end) {
offset = end;
continue;
}
const localTime = ms(Math.max(0, from - offset));
walkFrom(childId, items, localTime, callbacks, [...ancestors, { item, localTime: from }]);
offset = end;
}
break;
}
case Kind.Video:
callbacks.video(item, from, ancestors);
break;
case Kind.Image:
callbacks.image(item, from, ancestors);
break;
case Kind.Text:
callbacks.text(item, from, ancestors);
break;
case Kind.Caption:
callbacks.caption(item, from, ancestors);
break;
case Kind.Audio:
callbacks.audio(item, from, ancestors);
break;
}
}
export function computeItemDuration(id, timeline) {
return computeItemDurationFromMap(id, new Map(timeline.items.map(item => [item.id, item])));
}
function computeItemDurationFromMap(id, items) {
const item = items.get(id);
if (!item)
return ms(0);
switch (item.kind) {
case Kind.Sequence: {
let duration = ms(0);
for (const childId of item.childrenIds)
duration = ms(duration + computeItemDurationFromMap(childId, items));
return duration;
}
case Kind.Stack: {
let longest = ms(0);
for (const childId of item.childrenIds) {
const duration = computeItemDurationFromMap(childId, items);
if (duration > longest) {
longest = duration;
}
}
return longest;
}
default: {
if (!isPlayableItem(item))
return ms(0);
return item.duration;
}
}
}
export function computeOpacity(ctx, item, time) {
if (!("animationIds" in item) || item.animationIds === undefined)
return 1;
let opacity = 1;
for (const id of item.animationIds) {
const animation = ctx.items.get(id);
const anim = animation?.anims.opacity;
if (animation?.enabled !== false && anim && keyframesActiveAt(anim.track, time))
opacity = resolveScalarAnimation(time, anim);
}
return opacity;
}
function keyframesActiveAt(keys, time) {
if (keys.length === 0)
return false;
let start = keys[0][0];
let end = keys[0][0];
for (const [keyTime] of keys) {
start = Math.min(start, keyTime);
end = Math.max(end, keyTime);
}
return time >= start && time <= end;
}
function transformActiveAt(anim, time) {
return (keyframesActiveAt(anim.track.position.x, time) ||
keyframesActiveAt(anim.track.position.y, time) ||
keyframesActiveAt(anim.track.scale.x, time) ||
keyframesActiveAt(anim.track.scale.y, time) ||
keyframesActiveAt(anim.track.rotation, time));
}
//# sourceMappingURL=handy.js.map