UNPKG

@omnimedia/omnitool

Version:

open source video processing tools

261 lines (224 loc) 7.87 kB
import {TextStyleOptions} from "pixi.js" import {O} from "./o.js" import {Media} from "../parts/media.js" import {TimelineFile} from "../parts/basics.js" import {FilterAction} from "../parts/filters.js" import {TransitionName, transitions} from "../parts/transitions.js" import {filters, FilterParams, FilterType} from "../parts/filters.js" import {CaptionOptions, CaptionSourceItem} from "../parts/captions.js" import {Transcription} from "../../features/speech/transcribe/types.js" import {AnimationPreset, PresetOptions} from "../parts/animations/types.js" import {Crop, FilterableItem, Item, ItemBase, VisualAnimatableItem} from "../parts/item.js" import {animationPresets, visualAnimations} from "../parts/animations/registry.js" import {Anim, AnimateAction, Interpolation, Keyframes, TrackTransform, Transform, Vec2, VisualAnimationInput, VisualAnimations} from "../types.js" export type Build<T extends Item.Any = Item.Any> = (o: O) => T type BuildVisualAnimateActions = { [TKey in keyof VisualAnimations]-?: BuildAnimateAction<TKey> } type BuildPresetAnimateActions = { [TKey in AnimationPreset]: BuildPresetAnimateAction } type BuildTransitionActions = { [TKey in TransitionName]: (duration: number, options?: ItemBase) => Build<Item.Transition> } type ContainerInput = [label: string, ...items: Build[]] | Build[] function createTimeline(): TimelineFile { return { format: "timeline", info: "https://omniclip.app/", version: 0, items: [], rootId: 0 } } export function timeline(root: Build): TimelineFile { const o = new O({timeline: createTimeline()}) const item = root(o) o.timeline.rootId = item.id return o.timeline } export function sequence(...input: ContainerInput): Build<Item.Sequence> { const [first, ...rest] = input const label = typeof first === "string" ? first : undefined const items = (label ? rest : input) as Build[] return o => { const built = items.map(item => item(o)) return label ? o.sequence(label, ...built) : o.sequence(...built) } } export function stack(...input: ContainerInput): Build<Item.Stack> { const [first, ...rest] = input const label = typeof first === "string" ? first : undefined const items = (label ? rest : input) as Build[] return o => { const built = items.map(item => item(o)) return label ? o.stack(label, ...built) : o.stack(...built) } } export function video( media: Media, options?: { start?: number, duration?: number label?: string enabled?: boolean } ): Build<Item.Video> { return o => o.video(media, options) } export function image( media: Media, options?: { duration?: number label?: string enabled?: boolean } ): Build<Item.Image> { return o => o.image(media, options) } export function audio( media: Media, options?: { start?: number, duration?: number, gain?: number label?: string enabled?: boolean } ): Build<Item.Audio> { return o => o.audio(media, options) } export function text( content: string, options?: { duration?: number, styles?: TextStyleOptions label?: string enabled?: boolean } ): Build<Item.Text> { return o => o.text(content, options) } export function captions( item: Build<CaptionSourceItem>, transcript: Transcription, options?: CaptionOptions ): Build<Item.Stack> { return o => o.captions(item(o), transcript, options) } export function gap(duration: number, options?: ItemBase): Build<Item.Gap> { return o => o.gap(duration, options) } export function spatial(transform?: Transform, crop?: Crop): Build<Item.Spatial> { return o => o.spatial(transform, crop) } export const anim = { scalar(terp: Interpolation, track: Keyframes): Anim<Keyframes> { return {terp, track} }, vec2(terp: Interpolation, source: Keyframes<Vec2>): Anim<{x: Keyframes, y: Keyframes}> { const track = {x: [] as Keyframes, y: [] as Keyframes} for (const [time, [x, y]] of source) { track.x.push([time, x]) track.y.push([time, y]) } return {terp, track} }, transform: (terp: Interpolation, source: Keyframes<Transform>): Anim<TrackTransform> => { const track: TrackTransform = { 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} } } interface BuildFilterAction<TFilter extends FilterType> { <T extends FilterableItem>(item: Build<T>, params?: FilterParams<TFilter>): Build<T> make(params?: FilterParams<TFilter>): Build<Item.Filter<TFilter>> } interface BuildAnimateAction<TKey extends keyof VisualAnimations = keyof VisualAnimations> { <T extends VisualAnimatableItem>( item: Build<T>, terp: Interpolation, track: VisualAnimationInput<TKey> ): Build<T> make(terp: Interpolation, track: VisualAnimationInput<TKey>): Build<Item.Animation> } interface BuildPresetAnimateAction { <T extends VisualAnimatableItem>(item: Build<T>, options?: PresetOptions): Build<T> make(options?: PresetOptions): Build<Item.Animation> } type BuildFilterActions = { [TName in keyof typeof filters]: BuildFilterAction<(typeof filters)[TName]["type"]> } function makeFilter<TFilter extends FilterType>( get: (o: O) => FilterAction<TFilter> ): BuildFilterAction<TFilter> { const action = (<T extends FilterableItem>( item: Build<T>, params?: FilterParams<TFilter> ): Build<T> => o => get(o)(item(o), params)) as BuildFilterAction<TFilter> action.make = (params?: FilterParams<TFilter>) => o => get(o).make(params) return action } function makeFilters(): BuildFilterActions { const names = Object.keys(filters) as (keyof typeof filters)[] const entries = names.map(name => [ name, makeFilter(o => o.filter[name] as FilterAction<any>) ]) return Object.fromEntries(entries) as BuildFilterActions } export const filter = makeFilters() function makeAnimate<TKey extends keyof VisualAnimations>( get: (o: O) => AnimateAction<TKey> ): BuildAnimateAction<TKey> { const action = (<T extends VisualAnimatableItem>( item: Build<T>, terp: Interpolation, track: VisualAnimationInput<TKey> ): Build<T> => o => get(o)(item(o), terp, track)) as BuildAnimateAction<TKey> action.make = (terp: Interpolation, track: VisualAnimationInput<TKey>) => o => get(o).make(terp, track) return action } function makeAnimateActions(): BuildVisualAnimateActions { const entries = Object.keys(visualAnimations) .map(key => [key, makeAnimate(o => o.animate[key as keyof VisualAnimations] as AnimateAction<any>)]) return Object.fromEntries(entries) as BuildVisualAnimateActions } function makePresetAnimate( key: AnimationPreset ): BuildPresetAnimateAction { const action = (<T extends VisualAnimatableItem>( item: Build<T>, options?: PresetOptions ): Build<T> => o => o.animate.presets[key](item(o), options)) as BuildPresetAnimateAction action.make = (options?: PresetOptions) => o => o.animate.presets[key].make(options) return action } function makePresetAnimateActions(): BuildPresetAnimateActions { const entries = Object.keys(animationPresets) .map(key => [key, makePresetAnimate(key as AnimationPreset)]) return Object.fromEntries(entries) as BuildPresetAnimateActions } export const animate = { ...makeAnimateActions(), presets: makePresetAnimateActions(), } export function textStyle(style: TextStyleOptions): Build<Item.TextStyle> { return o => o.textStyle(style) } function makeTransitionActions(): BuildTransitionActions { const entries = Object.keys(transitions) .map(key => [key, (duration: number, options?: ItemBase) => (o: O) => o.transition[key as TransitionName](duration, options)]) return Object.fromEntries(entries) as BuildTransitionActions } export const transition = makeTransitionActions()