@omnimedia/omnitool 1.1.0-5 → 1.1.0-8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/s/demo/demo.css +5 -0
- package/s/demo/routines/transcode-test.ts +4 -2
- package/s/demo/routines/transitions-test.ts +2 -2
- package/s/driver/driver.ts +17 -9
- package/s/driver/fns/schematic.ts +44 -21
- package/s/driver/fns/work.ts +112 -97
- package/s/index.html.ts +6 -1
- package/s/timeline/index.ts +1 -0
- package/s/timeline/parts/basics.ts +1 -1
- package/s/timeline/parts/compositor/export.ts +77 -0
- package/s/timeline/parts/compositor/parts/html-tree.ts +37 -0
- package/s/timeline/parts/compositor/parts/schedulers.ts +85 -0
- package/s/timeline/parts/compositor/parts/tree-builder.ts +184 -0
- package/s/timeline/parts/compositor/parts/webcodecs-tree.ts +30 -0
- package/s/timeline/parts/compositor/playback.ts +81 -0
- package/s/timeline/parts/compositor/samplers/html.ts +115 -0
- package/s/timeline/parts/compositor/samplers/webcodecs.ts +60 -0
- package/s/timeline/parts/item.ts +38 -6
- package/s/timeline/parts/media.ts +21 -0
- package/s/timeline/parts/waveform.ts +1 -1
- package/s/timeline/sugar/builders.ts +102 -0
- package/s/timeline/sugar/o.ts +75 -16
- package/s/timeline/sugar/omni-test.ts +2 -2
- package/s/timeline/sugar/omni.ts +14 -11
- package/s/timeline/timeline.ts +22 -0
- package/s/timeline/types.ts +29 -0
- package/s/timeline/utils/audio-stream.ts +15 -0
- package/s/timeline/utils/matrix.ts +33 -0
- package/s/timeline/utils/video-cursor.ts +40 -0
- package/x/demo/demo.bundle.min.js +39 -37
- package/x/demo/demo.bundle.min.js.map +4 -4
- package/x/demo/demo.css +5 -0
- package/x/demo/routines/transcode-test.js +4 -2
- package/x/demo/routines/transcode-test.js.map +1 -1
- package/x/demo/routines/transitions-test.js +2 -2
- package/x/demo/routines/transitions-test.js.map +1 -1
- package/x/driver/driver.d.ts +3 -5
- package/x/driver/driver.js +16 -9
- package/x/driver/driver.js.map +1 -1
- package/x/driver/driver.worker.bundle.min.js +2537 -148
- package/x/driver/driver.worker.bundle.min.js.map +4 -4
- package/x/driver/fns/host.d.ts +9 -2
- package/x/driver/fns/schematic.d.ts +38 -20
- package/x/driver/fns/work.d.ts +11 -4
- package/x/driver/fns/work.js +105 -96
- package/x/driver/fns/work.js.map +1 -1
- package/x/features/speech/transcribe/worker.bundle.min.js +541 -541
- package/x/features/speech/transcribe/worker.bundle.min.js.map +4 -4
- package/x/index.html +13 -3
- package/x/index.html.js +6 -1
- package/x/index.html.js.map +1 -1
- package/x/timeline/index.d.ts +1 -0
- package/x/timeline/index.js +1 -0
- package/x/timeline/index.js.map +1 -1
- package/x/timeline/parts/basics.d.ts +1 -1
- package/x/timeline/parts/compositor/export.d.ts +9 -0
- package/x/timeline/parts/compositor/export.js +64 -0
- package/x/timeline/parts/compositor/export.js.map +1 -0
- package/x/timeline/parts/compositor/parts/html-tree.d.ts +3 -0
- package/x/timeline/parts/compositor/parts/html-tree.js +40 -0
- package/x/timeline/parts/compositor/parts/html-tree.js.map +1 -0
- package/x/timeline/parts/compositor/parts/schedulers.d.ts +15 -0
- package/x/timeline/parts/compositor/parts/schedulers.js +64 -0
- package/x/timeline/parts/compositor/parts/schedulers.js.map +1 -0
- package/x/timeline/parts/compositor/parts/tree-builder.d.ts +37 -0
- package/x/timeline/parts/compositor/parts/tree-builder.js +147 -0
- package/x/timeline/parts/compositor/parts/tree-builder.js.map +1 -0
- package/x/timeline/parts/compositor/parts/webcodecs-tree.d.ts +3 -0
- package/x/timeline/parts/compositor/parts/webcodecs-tree.js +28 -0
- package/x/timeline/parts/compositor/parts/webcodecs-tree.js.map +1 -0
- package/x/timeline/parts/compositor/playback.d.ts +19 -0
- package/x/timeline/parts/compositor/playback.js +71 -0
- package/x/timeline/parts/compositor/playback.js.map +1 -0
- package/x/timeline/parts/compositor/samplers/html.d.ts +3 -0
- package/x/timeline/parts/compositor/samplers/html.js +106 -0
- package/x/timeline/parts/compositor/samplers/html.js.map +1 -0
- package/x/timeline/parts/compositor/samplers/webcodecs.d.ts +2 -0
- package/x/timeline/parts/compositor/samplers/webcodecs.js +55 -0
- package/x/timeline/parts/compositor/samplers/webcodecs.js.map +1 -0
- package/x/timeline/parts/item.d.ts +34 -8
- package/x/timeline/parts/item.js +6 -3
- package/x/timeline/parts/item.js.map +1 -1
- package/x/timeline/parts/media.d.ts +3 -0
- package/x/timeline/parts/media.js +17 -0
- package/x/timeline/parts/media.js.map +1 -1
- package/x/timeline/parts/waveform.js +1 -1
- package/x/timeline/parts/waveform.js.map +1 -1
- package/x/timeline/sugar/builders.d.ts +96 -0
- package/x/timeline/sugar/builders.js +108 -0
- package/x/timeline/sugar/builders.js.map +1 -0
- package/x/timeline/sugar/o.d.ts +21 -8
- package/x/timeline/sugar/o.js +63 -14
- package/x/timeline/sugar/o.js.map +1 -1
- package/x/timeline/sugar/omni-test.js +1 -1
- package/x/timeline/sugar/omni-test.js.map +1 -1
- package/x/timeline/sugar/omni.d.ts +7 -3
- package/x/timeline/sugar/omni.js +9 -8
- package/x/timeline/sugar/omni.js.map +1 -1
- package/x/timeline/timeline.d.ts +9 -0
- package/x/timeline/timeline.js +22 -0
- package/x/timeline/timeline.js.map +1 -0
- package/x/timeline/types.d.ts +24 -0
- package/x/timeline/types.js +2 -0
- package/x/timeline/types.js.map +1 -0
- package/x/timeline/utils/audio-stream.d.ts +6 -0
- package/x/timeline/utils/audio-stream.js +17 -0
- package/x/timeline/utils/audio-stream.js.map +1 -0
- package/x/timeline/utils/matrix.d.ts +8 -0
- package/x/timeline/utils/matrix.js +26 -0
- package/x/timeline/utils/matrix.js.map +1 -0
- package/x/timeline/utils/video-cursor.d.ts +10 -0
- package/x/timeline/utils/video-cursor.js +36 -0
- package/x/timeline/utils/video-cursor.js.map +1 -0
- package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js +6 -6
- package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js.map +4 -4
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
|
|
2
|
+
import {ALL_FORMATS, Input} from "mediabunny"
|
|
3
|
+
|
|
2
4
|
import {Datafile} from "../utils/datafile.js"
|
|
5
|
+
import {DecoderSource} from "../../driver/fns/schematic.js"
|
|
6
|
+
import {loadDecoderSource} from "../../driver/utils/load-decoder-source.js"
|
|
3
7
|
|
|
4
8
|
export class Media {
|
|
5
9
|
duration = 0
|
|
10
|
+
hasVideo = false
|
|
11
|
+
hasAudio = false
|
|
12
|
+
|
|
6
13
|
constructor(public datafile: Datafile) {}
|
|
7
14
|
|
|
8
15
|
static async analyze(datafile: Datafile) {
|
|
9
16
|
const media = new this(datafile)
|
|
10
17
|
media.duration = 10
|
|
18
|
+
const {video, audio} = await this.#has("/assets/temp/gl.mp4")
|
|
19
|
+
media.hasAudio = audio
|
|
20
|
+
media.hasVideo = video
|
|
11
21
|
return media
|
|
12
22
|
}
|
|
23
|
+
|
|
24
|
+
static async #has(source: DecoderSource) {
|
|
25
|
+
const input = new Input({
|
|
26
|
+
formats: ALL_FORMATS,
|
|
27
|
+
source: await loadDecoderSource(source)
|
|
28
|
+
})
|
|
29
|
+
return {
|
|
30
|
+
audio: !!(await input.getPrimaryAudioTrack()),
|
|
31
|
+
video: !!(await input.getPrimaryVideoTrack())
|
|
32
|
+
}
|
|
33
|
+
}
|
|
13
34
|
}
|
|
14
35
|
|
|
@@ -21,7 +21,7 @@ export class Waveform {
|
|
|
21
21
|
|
|
22
22
|
static async init(source: DecoderSource, container: HTMLElement) {
|
|
23
23
|
const driver = await context.driver
|
|
24
|
-
const reader = driver.
|
|
24
|
+
const reader = driver.decodeAudio({source}).getReader()
|
|
25
25
|
|
|
26
26
|
const peaks: number[] = []
|
|
27
27
|
let buffer: number[] = []
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {O} from "./o.js"
|
|
2
|
+
import {Id} from "../parts/basics.js"
|
|
3
|
+
import {Item} from "../parts/item.js"
|
|
4
|
+
|
|
5
|
+
export class TimelineItem {
|
|
6
|
+
public readonly id: Id
|
|
7
|
+
|
|
8
|
+
constructor(public item: Item.Any) {
|
|
9
|
+
this.id = item.id
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
toJSON() {
|
|
13
|
+
return {
|
|
14
|
+
...this.item
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
abstract class VisualItem extends TimelineItem {
|
|
20
|
+
abstract spatial(spatial: Spatial): TimelineItem
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Stack extends VisualItem {
|
|
24
|
+
constructor(private o: O, public item: Item.Stack) {
|
|
25
|
+
super(item)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
spatial(spatial: Spatial) {
|
|
29
|
+
this.item.spatialId = spatial.item.id
|
|
30
|
+
return this
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
addChildren(fn: (o: O) => TimelineItem | TimelineItem[]) {
|
|
34
|
+
const result = fn(this.o)
|
|
35
|
+
const items = Array.isArray(result) ? result : [result]
|
|
36
|
+
this.item.childrenIds.push(...items.map(c => c.item.id))
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class Spatial extends TimelineItem {
|
|
42
|
+
constructor(public item: Item.Spatial) {super(item)}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class Gap extends TimelineItem {
|
|
46
|
+
constructor(public item: Item.Gap) {super(item)}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class Audio extends TimelineItem {
|
|
50
|
+
constructor(public item: Item.Audio) {super(item)}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class Video extends VisualItem {
|
|
54
|
+
constructor(public item: Item.Video) {
|
|
55
|
+
super(item)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
spatial(spatial: Spatial) {
|
|
59
|
+
this.item.spatialId = spatial.item.id
|
|
60
|
+
return this
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class Text extends VisualItem {
|
|
65
|
+
constructor(public item: Item.Text) {
|
|
66
|
+
super(item)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
color(color: string) {
|
|
70
|
+
this.item.color = color
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
spatial(spatial: Spatial) {
|
|
75
|
+
this.item.spatialId = spatial.item.id
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class Sequence extends VisualItem {
|
|
81
|
+
constructor(private o: O, public item: Item.Sequence) {
|
|
82
|
+
super(item)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
spatial(spatial: Spatial) {
|
|
86
|
+
this.item.spatialId = spatial.item.id
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
addChildren(fn: (o: O) => TimelineItem | TimelineItem[]) {
|
|
91
|
+
const result = fn(this.o)
|
|
92
|
+
const items = Array.isArray(result) ? result : [result]
|
|
93
|
+
this.item.childrenIds.push(...items.map(c => c.item.id))
|
|
94
|
+
return this
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class Transition extends TimelineItem {
|
|
99
|
+
constructor(public item: Item.Transition) {
|
|
100
|
+
super(item)
|
|
101
|
+
}
|
|
102
|
+
}
|
package/s/timeline/sugar/o.ts
CHANGED
|
@@ -3,16 +3,18 @@ import {MapG} from "@e280/stz"
|
|
|
3
3
|
import {Id} from "../parts/basics.js"
|
|
4
4
|
import {Media} from "../parts/media.js"
|
|
5
5
|
import {Effect, Item, Kind} from "../parts/item.js"
|
|
6
|
+
import {Transform, TransformOptions, Vec2} from "../types.js"
|
|
7
|
+
import {Video, Gap, Sequence, Stack, Text, TimelineItem, Spatial, Audio, Transition} from "./builders.js"
|
|
6
8
|
|
|
7
9
|
export class O {
|
|
8
10
|
#nextId = 0
|
|
9
|
-
#items = new MapG<Id,
|
|
11
|
+
#items = new MapG<Id, TimelineItem>()
|
|
10
12
|
|
|
11
13
|
#getId() {
|
|
12
14
|
return this.#nextId++
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
register(item:
|
|
17
|
+
register(item: TimelineItem) {
|
|
16
18
|
if (!this.#items.has(item.id))
|
|
17
19
|
this.#items.set(item.id, item)
|
|
18
20
|
return item.id
|
|
@@ -22,39 +24,96 @@ export class O {
|
|
|
22
24
|
return [...this.#items.values()]
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
get itemsMap() {
|
|
28
|
+
return this.#items
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
spatial = (transform: Transform) => {
|
|
32
|
+
const item: Item.Spatial = {
|
|
33
|
+
id: this.#getId(),
|
|
34
|
+
kind: Kind.Spatial,
|
|
35
|
+
transform
|
|
36
|
+
}
|
|
37
|
+
const spatial = new Spatial(item)
|
|
38
|
+
this.register(spatial)
|
|
39
|
+
return spatial
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sequence = (...items: TimelineItem[]) => new Sequence(this, {
|
|
26
43
|
id: this.#getId(),
|
|
27
44
|
kind: Kind.Sequence,
|
|
28
|
-
|
|
45
|
+
childrenIds: items.map((item) => this.register(item))
|
|
29
46
|
})
|
|
30
47
|
|
|
31
|
-
stack = (...items:
|
|
32
|
-
id: this.#getId(),
|
|
48
|
+
stack = (...items: TimelineItem[]) => new Stack(this, {
|
|
33
49
|
kind: Kind.Stack,
|
|
34
|
-
|
|
50
|
+
id: this.#getId(),
|
|
51
|
+
childrenIds: items.map(item => this.register(item))
|
|
35
52
|
})
|
|
36
53
|
|
|
37
|
-
|
|
54
|
+
video = (media: Media, options?: {start?: number, duration?: number}) => {
|
|
55
|
+
if(!media.hasVideo)
|
|
56
|
+
throw new Error(`Video clip error: media "${media.datafile.filename}" has no video track.`)
|
|
57
|
+
|
|
58
|
+
const item: Item.Video = {
|
|
59
|
+
kind: Kind.Video,
|
|
60
|
+
id: this.#getId(),
|
|
61
|
+
mediaHash: media.datafile.checksum.hash,
|
|
62
|
+
start: options?.start ?? 0,
|
|
63
|
+
duration: options?.duration ?? media.duration
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return new Video(item)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
audio = (media: Media, options?: {start?: number, duration?: number}) => {
|
|
70
|
+
if(!media.hasAudio)
|
|
71
|
+
throw new Error(`Audio clip error: media "${media.datafile.filename}" has no audio track.`)
|
|
72
|
+
|
|
73
|
+
const item: Item.Audio = {
|
|
74
|
+
kind: Kind.Audio,
|
|
75
|
+
id: this.#getId(),
|
|
76
|
+
mediaHash: media.datafile.checksum.hash,
|
|
77
|
+
start: options?.start ?? 0,
|
|
78
|
+
duration: options?.duration ?? media.duration
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return new Audio(item)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
text = (content: string) => new Text({
|
|
38
85
|
id: this.#getId(),
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
duration: duration ?? media.duration,
|
|
86
|
+
content,
|
|
87
|
+
kind: Kind.Text,
|
|
88
|
+
color: "#FFFFF"
|
|
43
89
|
})
|
|
44
90
|
|
|
45
|
-
|
|
91
|
+
gap = (duration: number) => new Gap({
|
|
46
92
|
id: this.#getId(),
|
|
47
|
-
kind: Kind.
|
|
48
|
-
|
|
93
|
+
kind: Kind.Gap,
|
|
94
|
+
duration
|
|
49
95
|
})
|
|
50
96
|
|
|
51
97
|
transition = {
|
|
52
|
-
crossfade: (duration: number)
|
|
98
|
+
crossfade: (duration: number) => new Transition({
|
|
53
99
|
id: this.#getId(),
|
|
54
100
|
kind: Kind.Transition,
|
|
55
101
|
effect: Effect.Crossfade,
|
|
56
102
|
duration,
|
|
57
103
|
}),
|
|
58
104
|
}
|
|
105
|
+
|
|
106
|
+
transform = (options?: TransformOptions): Transform => {
|
|
107
|
+
const position: Vec2 = [
|
|
108
|
+
options?.position?.[0] ?? 0,
|
|
109
|
+
options?.position?.[1] ?? 0
|
|
110
|
+
]
|
|
111
|
+
const scale: Vec2 = [
|
|
112
|
+
options?.scale?.[0] ?? 1,
|
|
113
|
+
options?.scale?.[1] ?? 1
|
|
114
|
+
]
|
|
115
|
+
const rotation = options?.rotation ?? 0
|
|
116
|
+
return [position, scale, rotation]
|
|
117
|
+
}
|
|
59
118
|
}
|
|
60
119
|
|
|
@@ -22,10 +22,10 @@ const {mediaA, mediaB} = await omni.load({
|
|
|
22
22
|
//
|
|
23
23
|
|
|
24
24
|
const timeline = omni.timeline(o => o.sequence(
|
|
25
|
-
o.
|
|
25
|
+
o.video(mediaA),
|
|
26
26
|
o.transition.crossfade(600),
|
|
27
27
|
o.stack(
|
|
28
|
-
o.
|
|
28
|
+
o.video(mediaB),
|
|
29
29
|
o.text("hello world"),
|
|
30
30
|
),
|
|
31
31
|
))
|
package/s/timeline/sugar/omni.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
|
|
2
2
|
import {O} from "./o.js"
|
|
3
|
-
import {
|
|
3
|
+
import {Timeline} from "../timeline.js"
|
|
4
4
|
import {Media} from "../parts/media.js"
|
|
5
|
-
import {
|
|
5
|
+
import {TimelineItem} from "./builders.js"
|
|
6
6
|
import {Datafile} from "../utils/datafile.js"
|
|
7
|
+
import {TimelineFile} from "../parts/basics.js"
|
|
8
|
+
import {Export} from "../parts/compositor/export.js"
|
|
7
9
|
import {ResourcePool} from "../parts/resource-pool.js"
|
|
10
|
+
import {RenderConfig} from "../../driver/fns/schematic.js"
|
|
8
11
|
|
|
9
12
|
export class Omni {
|
|
10
13
|
resources = new ResourcePool()
|
|
14
|
+
#export = new Export()
|
|
11
15
|
|
|
12
16
|
load = async<S extends Record<string, Promise<Datafile>>>(spec: S) => {
|
|
13
17
|
return Object.fromEntries(await Promise.all(Object.entries(spec).map(
|
|
@@ -15,16 +19,15 @@ export class Omni {
|
|
|
15
19
|
))) as {[K in keyof S]: Media}
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
timeline = (fn: (o: O) =>
|
|
22
|
+
timeline = (fn: (o: O) => TimelineItem): Timeline => {
|
|
19
23
|
const o = new O()
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
24
|
+
const root = fn(o)
|
|
25
|
+
o.register(root)
|
|
26
|
+
return new Timeline(root, o.itemsMap)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
render = async (timeline: TimelineFile, config: RenderConfig) => {
|
|
30
|
+
await this.#export.render(timeline)
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {TimelineItem} from "./sugar/builders.js"
|
|
2
|
+
import {Id, TimelineFile} from "./parts/basics.js"
|
|
3
|
+
|
|
4
|
+
export class Timeline {
|
|
5
|
+
constructor(public root: TimelineItem, private items: Map<Id, TimelineItem>) {}
|
|
6
|
+
|
|
7
|
+
require<T extends TimelineItem>(id: Id): T {
|
|
8
|
+
const item = this.items.get(id)
|
|
9
|
+
return item as T
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
toJSON(): TimelineFile {
|
|
13
|
+
return {
|
|
14
|
+
format: "timeline",
|
|
15
|
+
info: "https://omniclip.app/",
|
|
16
|
+
version: 0,
|
|
17
|
+
rootId: this.root.item.id,
|
|
18
|
+
items: Array.from(this.items.values()).map(item => item.toJSON())
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type Interpolation = "linear" | "catmullRom"
|
|
2
|
+
export type Keyframe<Value = number> = [time: number, value: Value]
|
|
3
|
+
export type Keyframes<Value = number> = Keyframe<Value>[]
|
|
4
|
+
export type Vec2 = [x: number, y: number]
|
|
5
|
+
export type Transform = [position: Vec2, scale: Vec2, rotation: number]
|
|
6
|
+
|
|
7
|
+
export type TrackVec2 = {
|
|
8
|
+
x: Keyframes
|
|
9
|
+
y: Keyframes
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type Anim<T> = {
|
|
13
|
+
terp: Interpolation
|
|
14
|
+
track: T
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type Animations = Anim<TrackTransform>
|
|
18
|
+
|
|
19
|
+
export type TrackTransform = {
|
|
20
|
+
position: TrackVec2
|
|
21
|
+
scale: TrackVec2
|
|
22
|
+
rotation: Keyframes
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type TransformOptions = {
|
|
26
|
+
position?: Vec2
|
|
27
|
+
scale?: Vec2
|
|
28
|
+
rotation?: number
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class AudioStream {
|
|
2
|
+
constructor(private reader: ReadableStreamDefaultReader<AudioData>) {}
|
|
3
|
+
|
|
4
|
+
async *stream(): AsyncGenerator<AudioData> {
|
|
5
|
+
while (true) {
|
|
6
|
+
const {done, value: hit} = await this.reader.read()
|
|
7
|
+
if (done) {
|
|
8
|
+
break
|
|
9
|
+
}
|
|
10
|
+
yield hit
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
cancel = async () => await this.reader.cancel()
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {Matrix} from "pixi.js"
|
|
2
|
+
import {Transform} from "../types.js"
|
|
3
|
+
|
|
4
|
+
export const transformToMat6 = (t: Transform): Mat6 => {
|
|
5
|
+
const [pos, scl, rotDeg] = t
|
|
6
|
+
const [x, y] = pos
|
|
7
|
+
const [sx, sy] = scl
|
|
8
|
+
const r = rotDeg * Math.PI / 180
|
|
9
|
+
const cos = Math.cos(r)
|
|
10
|
+
const sin = Math.sin(r)
|
|
11
|
+
return [cos * sx, sin * sx, -sin * sy, cos * sy, x, y]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const mat6ToMatrix = ([a, b, c, d, tx, ty]: Mat6): Matrix =>
|
|
15
|
+
new Matrix(a, b, c, d, tx, ty)
|
|
16
|
+
|
|
17
|
+
export const transformToMatrix = (t: Transform) => mat6ToMatrix(transformToMat6(t))
|
|
18
|
+
|
|
19
|
+
export const mul6 = (local: Mat6, parent: Mat6): Mat6 => {
|
|
20
|
+
const [a1, b1, c1, d1, tx1, ty1] = local
|
|
21
|
+
const [a2, b2, c2, d2, tx2, ty2] = parent
|
|
22
|
+
return [
|
|
23
|
+
a1 * a2 + c1 * b2,
|
|
24
|
+
b1 * a2 + d1 * b2,
|
|
25
|
+
a1 * c2 + c1 * d2,
|
|
26
|
+
b1 * c2 + d1 * d2,
|
|
27
|
+
a1 * tx2 + c1 * ty2 + tx1,
|
|
28
|
+
b1 * tx2 + d1 * ty2 + ty1
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const I6: Mat6 = [1, 0, 0, 1, 0, 0]
|
|
33
|
+
export type Mat6 = [a: number, b: number, c: number, d: number, tx: number, ty: number]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stateful, forward-only frame cursor for a single clip instance.
|
|
3
|
+
* It efficiently reads a video stream to find the frame nearest to a target timestamp.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class VideoCursor {
|
|
7
|
+
constructor(private reader: ReadableStreamDefaultReader<VideoFrame>) {}
|
|
8
|
+
|
|
9
|
+
async atOrNear(targetUs: number): Promise<VideoFrame | undefined> {
|
|
10
|
+
let prev: VideoFrame | null = null
|
|
11
|
+
while (true) {
|
|
12
|
+
const {done, value: hit} = await this.reader.read()
|
|
13
|
+
|
|
14
|
+
if (done) {
|
|
15
|
+
const out = prev ? new VideoFrame(prev) : undefined
|
|
16
|
+
prev?.close()
|
|
17
|
+
return out
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const hitUs = hit.timestamp ?? 0
|
|
21
|
+
if (hitUs >= targetUs) {
|
|
22
|
+
const prevUs = prev?.timestamp ?? Number.NEGATIVE_INFINITY
|
|
23
|
+
const usePrev = !!prev && Math.abs(prevUs - targetUs) < Math.abs(hitUs - targetUs)
|
|
24
|
+
|
|
25
|
+
const chosen = usePrev ? prev! : hit
|
|
26
|
+
const other = usePrev ? hit : prev
|
|
27
|
+
|
|
28
|
+
const copy = new VideoFrame(chosen)
|
|
29
|
+
chosen.close()
|
|
30
|
+
other?.close()
|
|
31
|
+
return copy
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
prev?.close()
|
|
35
|
+
prev = hit
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
cancel = async () => await this.reader.cancel()
|
|
40
|
+
}
|