@omnimedia/omnitool 1.1.0-4 → 1.1.0-7

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.
Files changed (133) hide show
  1. package/package.json +4 -2
  2. package/s/demo/demo.css +5 -0
  3. package/s/demo/routines/transcode-test.ts +4 -2
  4. package/s/demo/routines/transitions-test.ts +43 -0
  5. package/s/driver/driver.ts +17 -9
  6. package/s/driver/fns/schematic.ts +44 -21
  7. package/s/driver/fns/work.ts +112 -97
  8. package/s/features/transition/parts/fragment.ts +24 -0
  9. package/s/features/transition/parts/types.ts +94 -0
  10. package/s/features/transition/parts/uniforms.ts +29 -0
  11. package/s/features/transition/parts/vertex.ts +31 -0
  12. package/s/features/transition/transition.ts +60 -0
  13. package/s/index.html.ts +6 -1
  14. package/s/timeline/parts/basics.ts +1 -1
  15. package/s/timeline/parts/compositor/export.ts +77 -0
  16. package/s/timeline/parts/compositor/parts/html-tree.ts +37 -0
  17. package/s/timeline/parts/compositor/parts/schedulers.ts +85 -0
  18. package/s/timeline/parts/compositor/parts/tree-builder.ts +184 -0
  19. package/s/timeline/parts/compositor/parts/webcodecs-tree.ts +30 -0
  20. package/s/timeline/parts/compositor/playback.ts +81 -0
  21. package/s/timeline/parts/compositor/samplers/html.ts +115 -0
  22. package/s/timeline/parts/compositor/samplers/webcodecs.ts +60 -0
  23. package/s/timeline/parts/item.ts +38 -6
  24. package/s/timeline/parts/media.ts +21 -0
  25. package/s/timeline/parts/waveform.ts +1 -1
  26. package/s/timeline/sugar/builders.ts +102 -0
  27. package/s/timeline/sugar/o.ts +75 -16
  28. package/s/timeline/sugar/omni-test.ts +2 -2
  29. package/s/timeline/sugar/omni.ts +14 -11
  30. package/s/timeline/timeline.ts +22 -0
  31. package/s/timeline/types.ts +29 -0
  32. package/s/timeline/utils/audio-stream.ts +15 -0
  33. package/s/timeline/utils/matrix.ts +33 -0
  34. package/s/timeline/utils/video-cursor.ts +40 -0
  35. package/x/demo/demo.bundle.min.js +39 -37
  36. package/x/demo/demo.bundle.min.js.map +4 -4
  37. package/x/demo/demo.css +5 -0
  38. package/x/demo/routines/transcode-test.js +4 -2
  39. package/x/demo/routines/transcode-test.js.map +1 -1
  40. package/x/demo/routines/transitions-test.d.ts +5 -0
  41. package/x/demo/routines/transitions-test.js +35 -0
  42. package/x/demo/routines/transitions-test.js.map +1 -0
  43. package/x/driver/driver.d.ts +3 -5
  44. package/x/driver/driver.js +16 -9
  45. package/x/driver/driver.js.map +1 -1
  46. package/x/driver/driver.worker.bundle.min.js +2537 -148
  47. package/x/driver/driver.worker.bundle.min.js.map +4 -4
  48. package/x/driver/fns/host.d.ts +9 -2
  49. package/x/driver/fns/schematic.d.ts +38 -20
  50. package/x/driver/fns/work.d.ts +11 -4
  51. package/x/driver/fns/work.js +105 -96
  52. package/x/driver/fns/work.js.map +1 -1
  53. package/x/features/speech/transcribe/worker.bundle.min.js +541 -541
  54. package/x/features/speech/transcribe/worker.bundle.min.js.map +4 -4
  55. package/x/features/transition/parts/fragment.d.ts +1 -0
  56. package/x/features/transition/parts/fragment.js +25 -0
  57. package/x/features/transition/parts/fragment.js.map +1 -0
  58. package/x/features/transition/parts/types.d.ts +23 -0
  59. package/x/features/transition/parts/types.js +2 -0
  60. package/x/features/transition/parts/types.js.map +1 -0
  61. package/x/features/transition/parts/uniforms.d.ts +31 -0
  62. package/x/features/transition/parts/uniforms.js +27 -0
  63. package/x/features/transition/parts/uniforms.js.map +1 -0
  64. package/x/features/transition/parts/vertex.d.ts +1 -0
  65. package/x/features/transition/parts/vertex.js +32 -0
  66. package/x/features/transition/parts/vertex.js.map +1 -0
  67. package/x/features/transition/transition.d.ts +5 -0
  68. package/x/features/transition/transition.js +50 -0
  69. package/x/features/transition/transition.js.map +1 -0
  70. package/x/index.html +13 -3
  71. package/x/index.html.js +6 -1
  72. package/x/index.html.js.map +1 -1
  73. package/x/timeline/parts/basics.d.ts +1 -1
  74. package/x/timeline/parts/compositor/export.d.ts +9 -0
  75. package/x/timeline/parts/compositor/export.js +64 -0
  76. package/x/timeline/parts/compositor/export.js.map +1 -0
  77. package/x/timeline/parts/compositor/parts/html-tree.d.ts +3 -0
  78. package/x/timeline/parts/compositor/parts/html-tree.js +40 -0
  79. package/x/timeline/parts/compositor/parts/html-tree.js.map +1 -0
  80. package/x/timeline/parts/compositor/parts/schedulers.d.ts +15 -0
  81. package/x/timeline/parts/compositor/parts/schedulers.js +64 -0
  82. package/x/timeline/parts/compositor/parts/schedulers.js.map +1 -0
  83. package/x/timeline/parts/compositor/parts/tree-builder.d.ts +37 -0
  84. package/x/timeline/parts/compositor/parts/tree-builder.js +147 -0
  85. package/x/timeline/parts/compositor/parts/tree-builder.js.map +1 -0
  86. package/x/timeline/parts/compositor/parts/webcodecs-tree.d.ts +3 -0
  87. package/x/timeline/parts/compositor/parts/webcodecs-tree.js +28 -0
  88. package/x/timeline/parts/compositor/parts/webcodecs-tree.js.map +1 -0
  89. package/x/timeline/parts/compositor/playback.d.ts +19 -0
  90. package/x/timeline/parts/compositor/playback.js +71 -0
  91. package/x/timeline/parts/compositor/playback.js.map +1 -0
  92. package/x/timeline/parts/compositor/samplers/html.d.ts +3 -0
  93. package/x/timeline/parts/compositor/samplers/html.js +106 -0
  94. package/x/timeline/parts/compositor/samplers/html.js.map +1 -0
  95. package/x/timeline/parts/compositor/samplers/webcodecs.d.ts +2 -0
  96. package/x/timeline/parts/compositor/samplers/webcodecs.js +55 -0
  97. package/x/timeline/parts/compositor/samplers/webcodecs.js.map +1 -0
  98. package/x/timeline/parts/item.d.ts +34 -8
  99. package/x/timeline/parts/item.js +6 -3
  100. package/x/timeline/parts/item.js.map +1 -1
  101. package/x/timeline/parts/media.d.ts +3 -0
  102. package/x/timeline/parts/media.js +17 -0
  103. package/x/timeline/parts/media.js.map +1 -1
  104. package/x/timeline/parts/waveform.js +1 -1
  105. package/x/timeline/parts/waveform.js.map +1 -1
  106. package/x/timeline/sugar/builders.d.ts +96 -0
  107. package/x/timeline/sugar/builders.js +108 -0
  108. package/x/timeline/sugar/builders.js.map +1 -0
  109. package/x/timeline/sugar/o.d.ts +21 -8
  110. package/x/timeline/sugar/o.js +63 -14
  111. package/x/timeline/sugar/o.js.map +1 -1
  112. package/x/timeline/sugar/omni-test.js +1 -1
  113. package/x/timeline/sugar/omni-test.js.map +1 -1
  114. package/x/timeline/sugar/omni.d.ts +7 -3
  115. package/x/timeline/sugar/omni.js +9 -8
  116. package/x/timeline/sugar/omni.js.map +1 -1
  117. package/x/timeline/timeline.d.ts +9 -0
  118. package/x/timeline/timeline.js +22 -0
  119. package/x/timeline/timeline.js.map +1 -0
  120. package/x/timeline/types.d.ts +24 -0
  121. package/x/timeline/types.js +2 -0
  122. package/x/timeline/types.js.map +1 -0
  123. package/x/timeline/utils/audio-stream.d.ts +6 -0
  124. package/x/timeline/utils/audio-stream.js +17 -0
  125. package/x/timeline/utils/audio-stream.js.map +1 -0
  126. package/x/timeline/utils/matrix.d.ts +8 -0
  127. package/x/timeline/utils/matrix.js +26 -0
  128. package/x/timeline/utils/matrix.js.map +1 -0
  129. package/x/timeline/utils/video-cursor.d.ts +10 -0
  130. package/x/timeline/utils/video-cursor.js +36 -0
  131. package/x/timeline/utils/video-cursor.js.map +1 -0
  132. package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js +6 -6
  133. package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js.map +4 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnimedia/omnitool",
3
- "version": "1.1.0-4",
3
+ "version": "1.1.0-7",
4
4
  "description": "open source video processing tools",
5
5
  "license": "MIT",
6
6
  "author": "Przemysław Gałęzki",
@@ -37,7 +37,9 @@
37
37
  "@e280/stz": "^0.0.0-34",
38
38
  "@huggingface/transformers": "^3.7.1",
39
39
  "comrade": "^0.0.3",
40
- "mediabunny": "^1.4.4",
40
+ "gl-transitions": "^1.43.0",
41
+ "gsap": "^3.13.0",
42
+ "mediabunny": "^1.14.3",
41
43
  "mp4-muxer": "^5.2.1",
42
44
  "pixi.js": "^8.10.1",
43
45
  "wavesurfer.js": "^7.10.0",
package/s/demo/demo.css CHANGED
@@ -46,6 +46,11 @@
46
46
  }
47
47
  }
48
48
 
49
+ canvas {
50
+ width: 500px;
51
+ height: 300px;
52
+ }
53
+
49
54
  #filmstrip {
50
55
  display: flex;
51
56
  overflow-x: scroll;
@@ -10,7 +10,7 @@ export function setupTranscodeTest(driver: Driver, source: DecoderSource) {
10
10
  const ctx = canvas.getContext("2d")
11
11
 
12
12
  async function run() {
13
- const readables = driver.decode({
13
+ const video = driver.decodeVideo({
14
14
  source,
15
15
  async onFrame(frame) {
16
16
  const composed = await driver.composite([
@@ -30,9 +30,11 @@ export function setupTranscodeTest(driver: Driver, source: DecoderSource) {
30
30
  return composed
31
31
  }
32
32
  })
33
+ const audio = driver.decodeAudio({source})
33
34
 
34
35
  await driver.encode({
35
- readables,
36
+ video,
37
+ audio,
36
38
  config: {
37
39
  audio: {codec: "opus", bitrate: 128000},
38
40
  video: {codec: "vp9", bitrate: 1000000}
@@ -0,0 +1,43 @@
1
+ import {Application, Sprite} from "pixi.js"
2
+
3
+ import {Driver} from "../../driver/driver.js"
4
+ import {DecoderSource} from "../../driver/fns/schematic.js"
5
+ import {makeTransition} from "../../features/transition/transition.js"
6
+
7
+ export async function setupTransitionsTest(driver: Driver, source: DecoderSource) {
8
+ const app = new Application()
9
+ await app.init({width: 300, height: 300, preference: "webgl"})
10
+ const sprite = new Sprite({width: 300, height: 300})
11
+
12
+ app.stage.addChild(sprite)
13
+
14
+ document.body.appendChild(app.canvas)
15
+ const transition = makeTransition({name: "circle", renderer: app.renderer})
16
+
17
+ async function run() {
18
+ const video = driver.decodeVideo({
19
+ source,
20
+ async onFrame(frame) {
21
+ const texture = transition.render({
22
+ from: frame,
23
+ to: frame,
24
+ progress: 0.7,
25
+ width: app.canvas.width,
26
+ height: app.canvas.height
27
+ })
28
+ sprite.texture = texture
29
+ return frame
30
+ }
31
+ })
32
+
33
+ await driver.encode({
34
+ video,
35
+ config: {
36
+ audio: {codec: "opus", bitrate: 128000},
37
+ video: {codec: "vp9", bitrate: 1000000}
38
+ }
39
+ })
40
+ }
41
+
42
+ return {run}
43
+ }
@@ -50,7 +50,7 @@ export class Driver {
50
50
  return await videoTrack?.computeDuration()
51
51
  }
52
52
 
53
- decode(input: DecoderInput) {
53
+ decodeVideo(input: DecoderInput) {
54
54
  let lastFrame: VideoFrame | null = null
55
55
  const videoTransform = new TransformStream<VideoFrame, VideoFrame>({
56
56
  async transform(chunk, controller) {
@@ -61,19 +61,27 @@ export class Driver {
61
61
  lastFrame = frame
62
62
  }
63
63
  })
64
- const audioTransform = new TransformStream<AudioData, AudioData>()
65
- this.thread.work.decode[tune]({transfer: [videoTransform.writable, audioTransform.writable]})({
64
+ this.thread.work.decodeVideo[tune]({transfer: [videoTransform.writable]})({
66
65
  source: input.source,
67
66
  video: videoTransform.writable,
67
+ start: input.start,
68
+ end: input.end
69
+ })
70
+ return videoTransform.readable
71
+ }
72
+
73
+ decodeAudio(input: DecoderInput) {
74
+ const audioTransform = new TransformStream<AudioData, AudioData>()
75
+ this.thread.work.decodeAudio[tune]({transfer: [audioTransform.writable]})({
76
+ source: input.source,
68
77
  audio: audioTransform.writable,
78
+ start: input.start,
79
+ end: input.end
69
80
  })
70
- return {
71
- audio: audioTransform.readable,
72
- video: videoTransform.readable
73
- }
81
+ return audioTransform.readable
74
82
  }
75
83
 
76
- async encode({readables, config}: EncoderInput) {
84
+ async encode({video, audio, config}: EncoderInput) {
77
85
  const handle = await window.showSaveFilePicker()
78
86
  const writable = await handle.createWritable()
79
87
  // making bridge because file picker writable is not transferable
@@ -85,7 +93,7 @@ export class Driver {
85
93
  await writable.close()
86
94
  }
87
95
  })
88
- return await this.thread.work.encode[tune]({transfer: [readables.audio, readables.video, bridge]})({readables, config, bridge})
96
+ return await this.thread.work.encode[tune]({transfer: [audio ?? [], video ?? [], bridge]})({video, audio, config, bridge})
89
97
  }
90
98
 
91
99
  async composite(
@@ -2,16 +2,26 @@
2
2
  import {AsSchematic} from "@e280/comrade"
3
3
  import type {AudioEncodingConfig, StreamTargetChunk, VideoEncodingConfig} from "mediabunny"
4
4
 
5
+ import {Mat6} from "../../timeline/utils/matrix.js"
6
+
5
7
  export type DriverSchematic = AsSchematic<{
6
8
 
7
9
  // happens on the web worker
8
10
  work: {
9
11
  hello(): Promise<void>
10
12
 
11
- decode(input: {
13
+ decodeAudio(input: {
12
14
  source: DecoderSource
13
- video: WritableStream<VideoFrame>
14
15
  audio: WritableStream<AudioData>
16
+ start?: number
17
+ end?: number
18
+ }): Promise<void>
19
+
20
+ decodeVideo(input: {
21
+ source: DecoderSource
22
+ video: WritableStream<VideoFrame>
23
+ start?: number
24
+ end?: number
15
25
  }): Promise<void>
16
26
 
17
27
  encode(input: EncoderInput & {bridge: WritableStream<StreamTargetChunk>}): Promise<void>
@@ -26,20 +36,22 @@ export type DriverSchematic = AsSchematic<{
26
36
  }>
27
37
 
28
38
  export interface EncoderInput {
29
- readables: {
30
- video: ReadableStream<VideoFrame>
31
- audio: ReadableStream<AudioData>
32
- },
33
- config: {
34
- video: VideoEncodingConfig
35
- audio: AudioEncodingConfig
36
- }
39
+ video?: ReadableStream<VideoFrame>
40
+ audio?: ReadableStream<AudioData>
41
+ config: RenderConfig
42
+ }
43
+
44
+ export interface RenderConfig {
45
+ video: VideoEncodingConfig
46
+ audio: AudioEncodingConfig
37
47
  }
38
48
 
39
49
  export type DecoderSource = Blob | string | URL
40
50
 
41
51
  export interface DecoderInput {
42
52
  source: DecoderSource
53
+ start?: number
54
+ end?: number
43
55
  onFrame?: (frame: VideoFrame) => Promise<VideoFrame>
44
56
  }
45
57
 
@@ -59,25 +71,36 @@ export interface MuxOpts {
59
71
 
60
72
  export type Composition = Layer | (Layer | Composition)[]
61
73
 
62
- export type Transform = {
63
- x?: number
64
- y?: number
65
- scale?: number
66
- opacity?: number
67
- anchor?: number
68
- }
69
-
70
74
  export type TextLayer = {
71
75
  kind: 'text'
72
76
  content: string
73
77
  fontSize?: number
74
78
  color?: string
75
- } & Transform
79
+ matrix?: Mat6
80
+ }
76
81
 
77
82
  export type ImageLayer = {
78
83
  kind: 'image'
79
84
  frame: VideoFrame
80
- } & Transform
85
+ matrix?: Mat6
86
+ }
87
+
88
+ export type TransitionLayer = {
89
+ kind: 'transition'
90
+ name: string
91
+ progress: number
92
+ from: VideoFrame
93
+ to: VideoFrame
94
+ }
95
+
96
+ export type GapLayer = {
97
+ kind: 'gap'
98
+ }
99
+
100
+ export type Audio = {
101
+ kind: "audio"
102
+ data: AudioData
103
+ }
81
104
 
82
- export type Layer = TextLayer | ImageLayer
105
+ export type Layer = TextLayer | ImageLayer | TransitionLayer | GapLayer
83
106
 
@@ -1,88 +1,86 @@
1
1
  import {Comrade} from "@e280/comrade"
2
- import {autoDetectRenderer, Container, Renderer, Sprite, Text, Texture, DOMAdapter, WebWorkerAdapter} from "pixi.js"
2
+ import {autoDetectRenderer, Container, Renderer, Sprite, Text, Texture, DOMAdapter, WebWorkerAdapter, Matrix} from "pixi.js"
3
3
  import {Input, ALL_FORMATS, VideoSampleSink, Output, Mp4OutputFormat, VideoSampleSource, VideoSample, AudioSampleSink, AudioSampleSource, AudioSample, StreamTarget, BlobSource, UrlSource} from "mediabunny"
4
4
 
5
- import {Composition, DriverSchematic, Layer, Transform} from "./schematic.js"
5
+ import {Mat6, mat6ToMatrix} from "../../timeline/utils/matrix.js"
6
+ import {makeTransition} from "../../features/transition/transition.js"
7
+ import {Composition, DecoderSource, DriverSchematic, Layer} from "./schematic.js"
6
8
 
7
9
  DOMAdapter.set(WebWorkerAdapter)
8
10
 
11
+ const loadSource = async (source: DecoderSource) => {
12
+ if(source instanceof Blob) {
13
+ return new BlobSource(source)
14
+ } else {
15
+ return new UrlSource(source)
16
+ }
17
+ }
18
+
9
19
  export const setupDriverWork = (
10
20
  Comrade.work<DriverSchematic>(shell => ({
11
21
  async hello() {
12
22
  await shell.host.world()
13
23
  },
14
24
 
15
- async decode({source, video, audio}) {
16
- const loadSource = async () => {
17
- if(source instanceof Blob) {
18
- return new BlobSource(source)
19
- } else {
20
- return new UrlSource(source)
25
+ async decodeAudio({source, audio, start, end}) {
26
+ const input = new Input({
27
+ source: await loadSource(source),
28
+ formats: ALL_FORMATS
29
+ })
30
+
31
+ const audioTrack = await input.getPrimaryAudioTrack()
32
+ const audioDecodable = await audioTrack?.canDecode()
33
+ const audioWriter = audio.getWriter()
34
+
35
+ if (audioDecodable && audioTrack) {
36
+ const sink = new AudioSampleSink(audioTrack)
37
+ for await (const sample of sink.samples(start, end)) {
38
+ const frame = sample.toAudioData()
39
+ await audioWriter.write(frame)
40
+ sample.close()
41
+ frame.close()
21
42
  }
43
+ await audioWriter.close()
22
44
  }
45
+ },
46
+
47
+ async decodeVideo({source, video, start, end}) {
23
48
  const input = new Input({
24
- source: await loadSource(),
49
+ source: await loadSource(source),
25
50
  formats: ALL_FORMATS
26
51
  })
27
52
 
28
- const [videoTrack, audioTrack] = await Promise.all([
29
- input.getPrimaryVideoTrack(),
30
- input.getPrimaryAudioTrack()
31
- ])
32
-
53
+ const videoTrack = await input.getPrimaryVideoTrack()
33
54
  const videoDecodable = await videoTrack?.canDecode()
34
- const audioDecodable = await audioTrack?.canDecode()
35
-
36
55
  const videoWriter = video.getWriter()
37
- const audioWriter = audio.getWriter()
38
56
 
39
- await Promise.all([
40
- (async () => {
41
- if (videoDecodable && videoTrack) {
42
- const sink = new VideoSampleSink(videoTrack)
43
- for await (const sample of sink.samples()) {
44
- const frame = sample.toVideoFrame()
45
- await videoWriter.write(frame)
46
- sample.close()
47
- frame.close()
48
- }
49
- await videoWriter.close()
50
- }
51
- })(),
52
- (async () => {
53
- if (audioDecodable && audioTrack) {
54
- const sink = new AudioSampleSink(audioTrack)
55
- for await (const sample of sink.samples()) {
56
- const frame = sample.toAudioData()
57
- await audioWriter.write(frame)
58
- sample.close()
59
- frame.close()
60
- }
61
- await audioWriter.close()
62
- }
63
- })()
64
- ])
57
+ if (videoDecodable && videoTrack) {
58
+ const sink = new VideoSampleSink(videoTrack)
59
+ for await (const sample of sink.samples(start, end)) {
60
+ const frame = sample.toVideoFrame()
61
+ await videoWriter.write(frame)
62
+ sample.close()
63
+ frame.close()
64
+ }
65
+ await videoWriter.close()
66
+ }
65
67
  },
66
68
 
67
- async encode({readables, config, bridge}) {
69
+ async encode({video, audio, config, bridge}) {
68
70
  const output = new Output({
69
71
  format: new Mp4OutputFormat(),
70
72
  target: new StreamTarget(bridge, {chunked: true})
71
73
  })
72
- const videoSource = new VideoSampleSource(config.video)
73
- output.addVideoTrack(videoSource)
74
74
  // since AudioSample is not transferable it fails to transfer encoder bitrate config
75
75
  // so it needs to be hardcoded not set through constants eg QUALITY_LOW
76
- const audioSource = new AudioSampleSource(config.audio)
77
- output.addAudioTrack(audioSource)
78
76
 
79
- await output.start()
80
-
81
- const videoReader = readables.video.getReader()
82
- const audioReader = readables.audio.getReader()
77
+ const promises = []
83
78
 
84
- await Promise.all([
85
- (async () => {
79
+ if(video) {
80
+ const videoSource = new VideoSampleSource(config.video)
81
+ output.addVideoTrack(videoSource)
82
+ const videoReader = video.getReader()
83
+ promises.push((async () => {
86
84
  while (true) {
87
85
  const {done, value} = await videoReader.read()
88
86
  if (done) break
@@ -90,8 +88,14 @@ export const setupDriverWork = (
90
88
  await videoSource.add(sample)
91
89
  sample.close()
92
90
  }
93
- })(),
94
- (async () => {
91
+ })())
92
+ }
93
+
94
+ if(audio) {
95
+ const audioSource = new AudioSampleSource(config.audio)
96
+ output.addAudioTrack(audioSource)
97
+ const audioReader = audio.getReader()
98
+ promises.push((async () => {
95
99
  while (true) {
96
100
  const {done, value} = await audioReader.read()
97
101
  if (done) break
@@ -100,9 +104,11 @@ export const setupDriverWork = (
100
104
  sample.close()
101
105
  value.close()
102
106
  }
103
- })()
104
- ])
107
+ })())
108
+ }
105
109
 
110
+ await output.start()
111
+ await Promise.all(promises)
106
112
  await output.finalize()
107
113
  },
108
114
 
@@ -110,22 +116,18 @@ export const setupDriverWork = (
110
116
  const {stage, renderer} = await renderPIXI(1920, 1080)
111
117
  stage.removeChildren()
112
118
 
113
- const {baseFrame, disposables} = await renderLayer(composition, stage)
119
+ const {dispose} = await renderLayer(composition, stage)
114
120
  renderer.render(stage)
115
121
 
116
122
  // make sure browser support webgl/webgpu otherwise it might take much longer to construct frame
117
123
  // if its very slow on eg edge try chrome
118
124
  const frame = new VideoFrame(renderer.canvas, {
119
- timestamp: baseFrame?.timestamp,
120
- duration: baseFrame?.duration ?? undefined,
125
+ timestamp: 0,
126
+ duration: 0,
121
127
  })
122
128
 
123
- baseFrame?.close()
124
129
  renderer.clear()
125
-
126
- for (const disposable of disposables) {
127
- disposable.destroy(true)
128
- }
130
+ dispose()
129
131
 
130
132
  shell.transfer = [frame]
131
133
  return frame
@@ -157,46 +159,43 @@ async function renderPIXI(width: number, height: number) {
157
159
  return pixi
158
160
  }
159
161
 
162
+ const transitions: Map<string, ReturnType<typeof makeTransition>> = new Map()
163
+
160
164
  type RenderableObject = Sprite | Text | Texture
161
165
 
162
166
  async function renderLayer(
163
167
  layer: Layer | Composition,
164
168
  parent: Container,
165
- disposables: RenderableObject[] = []
166
169
  ) {
167
170
  if (Array.isArray(layer)) {
168
- let baseFrame: VideoFrame | undefined
171
+ const disposers: (() => void)[] = []
169
172
  for (const child of layer) {
170
- const result = await renderLayer(child, parent, disposables)
171
- baseFrame ??= result.baseFrame
173
+ const result = await renderLayer(child, parent)
174
+ disposers.push(result.dispose)
172
175
  }
173
- return {baseFrame, disposables}
174
- }
175
-
176
- if (!isRenderableLayer(layer)) {
177
- console.warn('Invalid layer', layer)
178
- return {disposables}
176
+ return {dispose: () => disposers.forEach(d => d())}
179
177
  }
180
178
 
181
179
  switch (layer.kind) {
182
180
  case 'text':
183
- return renderTextLayer(layer, parent, disposables)
181
+ return renderTextLayer(layer, parent)
184
182
  case 'image':
185
- return renderImageLayer(layer, parent, disposables)
183
+ return renderImageLayer(layer, parent)
184
+ case 'transition':
185
+ return renderTransitionLayer(layer, parent)
186
+ case 'gap': {
187
+ pixi?.renderer.clear()
188
+ return {dispose: () => {}}
189
+ }
186
190
  default:
187
191
  console.warn('Unknown layer kind', (layer as any).kind)
188
- return {disposables}
192
+ return {dispose: () => {}}
189
193
  }
190
194
  }
191
195
 
192
- function isRenderableLayer(layer: any): layer is Layer {
193
- return !!layer && typeof layer === 'object' && typeof layer.kind === 'string'
194
- }
195
-
196
196
  function renderTextLayer(
197
197
  layer: Extract<Layer, {kind: 'text'}>,
198
198
  parent: Container,
199
- disposables: RenderableObject[]
200
199
  ) {
201
200
  const text = new Text({
202
201
  text: layer.content,
@@ -206,29 +205,45 @@ function renderTextLayer(
206
205
  fill: layer.color ?? 'white'
207
206
  }
208
207
  })
209
- applyTransform(text, layer)
208
+ applyTransform(text, layer.matrix)
210
209
  parent.addChild(text)
211
- disposables.push(text)
212
- return {disposables}
210
+ return {dispose: () => text.destroy(true)}
213
211
  }
214
212
 
215
213
  function renderImageLayer(
216
214
  layer: Extract<Layer, {kind: 'image'}>,
217
215
  parent: Container,
218
- disposables: RenderableObject[]
219
216
  ) {
220
217
  const texture = Texture.from(layer.frame)
221
218
  const sprite = new Sprite(texture)
222
- applyTransform(sprite, layer)
219
+ applyTransform(sprite, layer.matrix)
220
+ parent.addChild(sprite)
221
+ return {dispose: () => {
222
+ sprite.destroy(true)
223
+ texture.destroy(true)
224
+ layer.frame.close()
225
+ }}
226
+ }
227
+
228
+ function renderTransitionLayer(
229
+ {from, to, progress, name}: Extract<Layer, {kind: 'transition'}>,
230
+ parent: Container,
231
+ ) {
232
+ const transition = transitions.get(name) ??
233
+ (transitions.set(name, makeTransition({
234
+ name: "circle",
235
+ renderer: pixi!.renderer
236
+ })),
237
+ transitions.get(name)!
238
+ )
239
+ const texture = transition.render({from, to, progress, width: from.displayWidth, height: from.displayHeight})
240
+ const sprite = new Sprite(texture)
223
241
  parent.addChild(sprite)
224
- disposables.push(sprite, texture)
225
- return {baseFrame: layer.frame, disposables}
242
+ return {dispose: () => sprite.destroy(false)}
226
243
  }
227
244
 
228
- function applyTransform(target: Sprite | Text, t: Transform = {}) {
229
- if(t.x) target.x = t.x
230
- if(t.y) target.y = t.y
231
- if(t.scale) target.scale.set(t.scale)
232
- if(t.opacity) target.alpha = t.opacity
233
- if(t.anchor && 'anchor' in target) target.anchor.set(t.anchor)
245
+ function applyTransform(target: Sprite | Text, worldMatrix?: Mat6) {
246
+ if (!worldMatrix) return
247
+ const mx = mat6ToMatrix(worldMatrix)
248
+ target.setFromMatrix(mx)
234
249
  }
@@ -0,0 +1,24 @@
1
+ export const fragment = (glsl: string) => `
2
+ precision highp float;
3
+ varying vec2 vTextureCoord;
4
+ varying vec2 _uv;
5
+ uniform sampler2D from, to;
6
+ uniform float progress, ratio, _fromR, _toR;
7
+ uniform float customUniform;
8
+
9
+ vec4 getFromColor(vec2 uv){
10
+ return texture2D(from, .5+(uv-.5)*vec2(max(ratio/_fromR,1.), max(_fromR/ratio,1.)));
11
+ }
12
+ vec4 getToColor(vec2 uv){
13
+ return texture2D(to, .5+(uv-.5)*vec2(max(ratio/_toR,1.), max(_toR/ratio,1.)));
14
+ }
15
+
16
+ // gl-transition code here
17
+ ${glsl}
18
+ // gl-transition code end
19
+
20
+ void main(){
21
+ vec2 uv = vTextureCoord.xy;
22
+ gl_FragColor = transition(vTextureCoord);
23
+ }
24
+ `
@@ -0,0 +1,94 @@
1
+ import {Renderer} from "pixi.js"
2
+
3
+ export interface TransitionOptions {
4
+ name: Transition
5
+ renderer: Renderer
6
+ }
7
+
8
+ export interface TransitionRendererOptions {
9
+ from: VideoFrame
10
+ to: VideoFrame
11
+ progress: number
12
+ width: number
13
+ height: number
14
+ }
15
+
16
+ export interface GLTransition {
17
+ author: string
18
+ createdAt: string
19
+ glsl: string
20
+ license: string
21
+ name: Transition
22
+ updatedAt: string
23
+ defaultParams: any
24
+ paramsTypes: any
25
+ }
26
+
27
+ export type Transition =
28
+ | "Bounce"
29
+ | "BowTieHorizontal"
30
+ | "BowTieVertical"
31
+ | "ButterflyWaveScrawler"
32
+ | "CircleCrop"
33
+ | "ColourDistance"
34
+ | "CrazyParametricFun"
35
+ | "CrossZoom"
36
+ | "Directional"
37
+ | "DoomScreenTransition"
38
+ | "Dreamy"
39
+ | "DreamyZoom"
40
+ | "GlitchDisplace"
41
+ | "GlitchMemories"
42
+ | "GridFlip"
43
+ | "InvertedPageCurl"
44
+ | "LinearBlur"
45
+ | "Mosaic"
46
+ | "PolkaDotsCurtain"
47
+ | "Radial"
48
+ | "SimpleZoom"
49
+ | "StereoViewer"
50
+ | "Swirl"
51
+ | "WaterDrop"
52
+ | "ZoomInCircles"
53
+ | "angular"
54
+ | "burn"
55
+ | "cannabisleaf"
56
+ | "circle"
57
+ | "circleopen"
58
+ | "colorphase"
59
+ | "crosshatch"
60
+ | "crosswarp"
61
+ | "cube"
62
+ | "directionalwarp"
63
+ | "directionalwipe"
64
+ | "displacement"
65
+ | "doorway"
66
+ | "fade"
67
+ | "fadecolor"
68
+ | "fadegrayscale"
69
+ | "flyeye"
70
+ | "heart"
71
+ | "hexagonalize"
72
+ | "kaleidoscope"
73
+ | "luma"
74
+ | "luminance_melt"
75
+ | "morph"
76
+ | "multiply_blend"
77
+ | "perlin"
78
+ | "pinwheel"
79
+ | "pixelize"
80
+ | "polar_function"
81
+ | "randomsquares"
82
+ | "ripple"
83
+ | "rotate_scale_fade"
84
+ | "squareswire"
85
+ | "squeeze"
86
+ | "swap"
87
+ | "undulatingBurnOut"
88
+ | "wind"
89
+ | "windowblinds"
90
+ | "windowslice"
91
+ | "wipeDown"
92
+ | "wipeLeft"
93
+ | "wipeRight"
94
+ | "wipeUp"