UNPKG

@omnimedia/omnitool

Version:

open source video processing tools

263 lines 10.1 kB
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