@omnimedia/omnitool 1.1.0-43 → 1.1.0-44

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 (179) hide show
  1. package/package.json +17 -18
  2. package/s/demo/demo.bundle.ts +20 -40
  3. package/s/demo/routines/export-test.ts +8 -0
  4. package/s/demo/routines/playback-test.ts +21 -0
  5. package/s/demo/routines/timeline-setup.ts +24 -0
  6. package/s/demo/routines/transcode-test.ts +2 -0
  7. package/s/driver/driver-worker.ts +9 -0
  8. package/s/driver/driver.test.ts +1 -1
  9. package/s/driver/driver.ts +8 -22
  10. package/s/driver/fns/schematic.ts +6 -2
  11. package/s/driver/fns/work.ts +2 -133
  12. package/s/driver/parts/compositor.ts +178 -0
  13. package/s/driver/parts/machina.ts +19 -20
  14. package/s/index.ts +1 -0
  15. package/s/timeline/index.ts +1 -1
  16. package/s/timeline/parts/media.ts +11 -2
  17. package/s/timeline/parts/{compositor → renderers}/parts/html-tree.ts +2 -2
  18. package/s/timeline/parts/{compositor → renderers}/parts/tree-builder.ts +4 -2
  19. package/s/timeline/parts/{compositor → renderers}/playback.ts +4 -13
  20. package/s/timeline/parts/{compositor → renderers}/samplers/html.ts +19 -5
  21. package/s/timeline/parts/{compositor → renderers}/samplers/webcodecs.ts +1 -1
  22. package/s/timeline/parts/resource-pool.ts +8 -4
  23. package/s/timeline/parts/resource.ts +1 -0
  24. package/s/timeline/sugar/o.ts +8 -4
  25. package/s/timeline/sugar/omni-test.ts +14 -14
  26. package/s/timeline/sugar/omni.ts +2 -2
  27. package/s/timeline/utils/datafile.ts +14 -4
  28. package/s/timeline/utils/dummy-data.ts +3 -3
  29. package/x/demo/WebGLRenderer-Q3OV2JVE.js +2 -0
  30. package/x/demo/WebGLRenderer-Q3OV2JVE.js.map +7 -0
  31. package/x/demo/WebGPURenderer-FUFF62QA.js +2 -0
  32. package/x/demo/WebGPURenderer-FUFF62QA.js.map +7 -0
  33. package/x/demo/browserAll-PGQYU756.js +2 -0
  34. package/x/demo/browserAll-PGQYU756.js.map +7 -0
  35. package/x/demo/chunk-2RBLPWNG.js +393 -0
  36. package/x/demo/chunk-2RBLPWNG.js.map +7 -0
  37. package/x/demo/chunk-6DBMQOFE.js +42 -0
  38. package/x/demo/chunk-6DBMQOFE.js.map +7 -0
  39. package/x/demo/chunk-LAJHJD2S.js +2 -0
  40. package/x/demo/chunk-LAJHJD2S.js.map +7 -0
  41. package/x/demo/chunk-LQU5JKKZ.js +269 -0
  42. package/x/demo/chunk-LQU5JKKZ.js.map +7 -0
  43. package/x/demo/chunk-RFNLITDQ.js +327 -0
  44. package/x/demo/chunk-RFNLITDQ.js.map +7 -0
  45. package/x/demo/chunk-TBWCKYN2.js +2 -0
  46. package/x/demo/chunk-TBWCKYN2.js.map +7 -0
  47. package/x/demo/chunk-TLDBHU4V.js +15 -0
  48. package/x/demo/chunk-TLDBHU4V.js.map +7 -0
  49. package/x/demo/chunk-X2GHKWPJ.js +157 -0
  50. package/x/demo/chunk-X2GHKWPJ.js.map +7 -0
  51. package/x/demo/demo.bundle.js +18 -27
  52. package/x/demo/demo.bundle.js.map +1 -1
  53. package/x/demo/demo.bundle.min.js +2378 -534
  54. package/x/demo/demo.bundle.min.js.map +4 -4
  55. package/x/demo/routines/export-test.d.ts +2 -0
  56. package/x/demo/routines/export-test.js +7 -0
  57. package/x/demo/routines/export-test.js.map +1 -0
  58. package/x/demo/routines/playback-test.d.ts +3 -0
  59. package/x/demo/routines/playback-test.js +17 -0
  60. package/x/demo/routines/playback-test.js.map +1 -0
  61. package/x/demo/routines/timeline-setup.d.ts +6 -0
  62. package/x/demo/routines/timeline-setup.js +13 -0
  63. package/x/demo/routines/timeline-setup.js.map +1 -0
  64. package/x/demo/routines/transcode-test.js +2 -0
  65. package/x/demo/routines/transcode-test.js.map +1 -1
  66. package/x/demo/webworkerAll-3YNCLHCR.js +2 -0
  67. package/x/demo/webworkerAll-3YNCLHCR.js.map +7 -0
  68. package/x/driver/WebGLRenderer-OMRWYQIV.js +2 -0
  69. package/x/driver/WebGLRenderer-OMRWYQIV.js.map +7 -0
  70. package/x/driver/WebGPURenderer-KQJB2OJJ.js +2 -0
  71. package/x/driver/WebGPURenderer-KQJB2OJJ.js.map +7 -0
  72. package/x/driver/browserAll-YBZEJCN3.js +2 -0
  73. package/x/driver/browserAll-YBZEJCN3.js.map +7 -0
  74. package/x/driver/chunk-3L3MB5NY.js +393 -0
  75. package/x/driver/chunk-3L3MB5NY.js.map +7 -0
  76. package/x/driver/chunk-42BQ4XKE.js +269 -0
  77. package/x/driver/chunk-42BQ4XKE.js.map +7 -0
  78. package/x/driver/chunk-4HAYG3N5.js +327 -0
  79. package/x/driver/chunk-4HAYG3N5.js.map +7 -0
  80. package/x/driver/chunk-BFBY7VYB.js +42 -0
  81. package/x/driver/chunk-BFBY7VYB.js.map +7 -0
  82. package/x/driver/chunk-KM6O72WE.js +157 -0
  83. package/x/driver/chunk-KM6O72WE.js.map +7 -0
  84. package/x/driver/chunk-N6HD4WYJ.js +2 -0
  85. package/x/driver/chunk-N6HD4WYJ.js.map +7 -0
  86. package/x/driver/chunk-WCZ2O3UN.js +15 -0
  87. package/x/driver/chunk-WCZ2O3UN.js.map +7 -0
  88. package/x/driver/chunk-XWNSF3WJ.js +2 -0
  89. package/x/driver/chunk-XWNSF3WJ.js.map +7 -0
  90. package/x/driver/driver-worker.d.ts +1 -0
  91. package/x/driver/driver-worker.js +6 -0
  92. package/x/driver/driver-worker.js.map +1 -0
  93. package/x/driver/driver.d.ts +5 -4
  94. package/x/driver/driver.js +10 -20
  95. package/x/driver/driver.js.map +1 -1
  96. package/x/driver/driver.test.js +1 -1
  97. package/x/driver/driver.test.js.map +1 -1
  98. package/x/driver/driver.worker.bundle.min.js +119 -3504
  99. package/x/driver/driver.worker.bundle.min.js.map +4 -4
  100. package/x/driver/fns/host.d.ts +1 -2
  101. package/x/driver/fns/schematic.d.ts +6 -1
  102. package/x/driver/fns/work.d.ts +2 -4
  103. package/x/driver/fns/work.js +1 -100
  104. package/x/driver/fns/work.js.map +1 -1
  105. package/x/driver/parts/compositor.d.ts +15 -0
  106. package/x/driver/parts/compositor.js +152 -0
  107. package/x/driver/parts/compositor.js.map +1 -0
  108. package/x/driver/parts/machina.d.ts +0 -20
  109. package/x/driver/parts/machina.js +6 -10
  110. package/x/driver/parts/machina.js.map +1 -1
  111. package/x/driver/webworkerAll-BKJQW6P7.js +2 -0
  112. package/x/driver/webworkerAll-BKJQW6P7.js.map +7 -0
  113. package/x/features/speech/transcribe/parts/prep-audio.d.ts +1 -1
  114. package/x/features/speech/transcribe/worker.bundle.min.js +899 -899
  115. package/x/features/speech/transcribe/worker.bundle.min.js.map +4 -4
  116. package/x/index.d.ts +1 -0
  117. package/x/index.html +2 -2
  118. package/x/index.js +1 -0
  119. package/x/index.js.map +1 -1
  120. package/x/timeline/index.d.ts +1 -1
  121. package/x/timeline/index.js +1 -1
  122. package/x/timeline/index.js.map +1 -1
  123. package/x/timeline/parts/filmstrip.d.ts +1 -1
  124. package/x/timeline/parts/media.d.ts +2 -0
  125. package/x/timeline/parts/media.js +10 -2
  126. package/x/timeline/parts/media.js.map +1 -1
  127. package/x/timeline/parts/renderers/export.js.map +1 -0
  128. package/x/timeline/parts/{compositor → renderers}/parts/html-tree.js +2 -2
  129. package/x/timeline/parts/renderers/parts/html-tree.js.map +1 -0
  130. package/x/timeline/parts/renderers/parts/schedulers.js.map +1 -0
  131. package/x/timeline/parts/{compositor → renderers}/parts/tree-builder.js +4 -2
  132. package/x/timeline/parts/renderers/parts/tree-builder.js.map +1 -0
  133. package/x/timeline/parts/renderers/parts/webcodecs-tree.js.map +1 -0
  134. package/x/timeline/parts/{compositor → renderers}/playback.d.ts +2 -4
  135. package/x/timeline/parts/{compositor → renderers}/playback.js +5 -14
  136. package/x/timeline/parts/renderers/playback.js.map +1 -0
  137. package/x/timeline/parts/{compositor → renderers}/samplers/html.js +14 -5
  138. package/x/timeline/parts/renderers/samplers/html.js.map +1 -0
  139. package/x/timeline/parts/{compositor → renderers}/samplers/webcodecs.js +1 -1
  140. package/x/timeline/parts/renderers/samplers/webcodecs.js.map +1 -0
  141. package/x/timeline/parts/resource-pool.d.ts +2 -0
  142. package/x/timeline/parts/resource-pool.js +7 -4
  143. package/x/timeline/parts/resource-pool.js.map +1 -1
  144. package/x/timeline/parts/resource.d.ts +1 -0
  145. package/x/timeline/sugar/o.d.ts +4 -1
  146. package/x/timeline/sugar/o.js +4 -4
  147. package/x/timeline/sugar/o.js.map +1 -1
  148. package/x/timeline/sugar/omni-test.js +14 -7
  149. package/x/timeline/sugar/omni-test.js.map +1 -1
  150. package/x/timeline/sugar/omni.d.ts +1 -2
  151. package/x/timeline/sugar/omni.js +2 -2
  152. package/x/timeline/sugar/omni.js.map +1 -1
  153. package/x/timeline/utils/datafile.d.ts +4 -3
  154. package/x/timeline/utils/datafile.js +16 -5
  155. package/x/timeline/utils/datafile.js.map +1 -1
  156. package/x/timeline/utils/dummy-data.d.ts +1 -2
  157. package/x/timeline/utils/dummy-data.js +4 -2
  158. package/x/timeline/utils/dummy-data.js.map +1 -1
  159. package/x/timeline/parts/compositor/export.js.map +0 -1
  160. package/x/timeline/parts/compositor/parts/html-tree.js.map +0 -1
  161. package/x/timeline/parts/compositor/parts/schedulers.js.map +0 -1
  162. package/x/timeline/parts/compositor/parts/tree-builder.js.map +0 -1
  163. package/x/timeline/parts/compositor/parts/webcodecs-tree.js.map +0 -1
  164. package/x/timeline/parts/compositor/playback.js.map +0 -1
  165. package/x/timeline/parts/compositor/samplers/html.js.map +0 -1
  166. package/x/timeline/parts/compositor/samplers/webcodecs.js.map +0 -1
  167. /package/s/timeline/parts/{compositor → renderers}/export.ts +0 -0
  168. /package/s/timeline/parts/{compositor → renderers}/parts/schedulers.ts +0 -0
  169. /package/s/timeline/parts/{compositor → renderers}/parts/webcodecs-tree.ts +0 -0
  170. /package/x/timeline/parts/{compositor → renderers}/export.d.ts +0 -0
  171. /package/x/timeline/parts/{compositor → renderers}/export.js +0 -0
  172. /package/x/timeline/parts/{compositor → renderers}/parts/html-tree.d.ts +0 -0
  173. /package/x/timeline/parts/{compositor → renderers}/parts/schedulers.d.ts +0 -0
  174. /package/x/timeline/parts/{compositor → renderers}/parts/schedulers.js +0 -0
  175. /package/x/timeline/parts/{compositor → renderers}/parts/tree-builder.d.ts +0 -0
  176. /package/x/timeline/parts/{compositor → renderers}/parts/webcodecs-tree.d.ts +0 -0
  177. /package/x/timeline/parts/{compositor → renderers}/parts/webcodecs-tree.js +0 -0
  178. /package/x/timeline/parts/{compositor → renderers}/samplers/html.d.ts +0 -0
  179. /package/x/timeline/parts/{compositor → renderers}/samplers/webcodecs.d.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnimedia/omnitool",
3
- "version": "1.1.0-43",
3
+ "version": "1.1.0-44",
4
4
  "description": "open source video processing tools",
5
5
  "license": "MIT",
6
6
  "author": "Przemysław Gałęzki",
@@ -23,29 +23,28 @@
23
23
  "test-debug": "node inspect x/tests.test.js"
24
24
  },
25
25
  "devDependencies": {
26
- "@e280/science": "^0.0.6",
27
- "@e280/scute": "^0.0.0-7",
28
- "@types/node": "^24.2.0",
26
+ "@e280/science": "^0.1.4",
27
+ "@e280/scute": "^0.1.2",
28
+ "@types/node": "^25.0.3",
29
29
  "http-server": "^14.1.1",
30
30
  "npm-run-all": "^4.1.5",
31
- "typescript": "^5.9.2"
31
+ "typescript": "^5.9.3"
32
32
  },
33
33
  "dependencies": {
34
- "@e280/comrade": "^0.0.0-23",
35
- "@e280/renraku": "^0.5.0-29",
36
- "@e280/sly": "^0.2.0-32",
37
- "@e280/strata": "^0.2.1",
38
- "@e280/stz": "^0.2.13",
39
- "@huggingface/transformers": "^3.7.1",
34
+ "@e280/comrade": "^0.1.0",
35
+ "@e280/renraku": "^0.5.3",
36
+ "@e280/sly": "^0.2.5",
37
+ "@e280/strata": "^0.2.5",
38
+ "@e280/stz": "^0.2.15",
39
+ "@huggingface/transformers": "^3.8.1",
40
40
  "comrade": "^0.0.3",
41
41
  "gl-transitions": "^1.43.0",
42
- "gsap": "^3.13.0",
43
- "lit": "^3.3.1",
44
- "mediabunny": "^1.14.3",
45
- "mp4-muxer": "^5.2.1",
46
- "pixi.js": "^8.10.1",
47
- "wavesurfer.js": "^7.10.0",
48
- "web-demuxer": "^2.3.8"
42
+ "gsap": "^3.14.2",
43
+ "lit": "^3.3.2",
44
+ "mediabunny": "^1.27.3",
45
+ "mp4-muxer": "^5.2.2",
46
+ "pixi.js": "^8.14.3",
47
+ "wavesurfer.js": "^7.12.1"
49
48
  },
50
49
  "homepage": "https://github.com/omni-media/omnitool#readme",
51
50
  "repository": {
@@ -1,48 +1,20 @@
1
1
 
2
2
  import {Driver} from "../driver/driver.js"
3
+ import {exportTest} from "./routines/export-test.js"
4
+ import {playbackTest} from "./routines/playback-test.js"
3
5
  import {waveformTest} from "./routines/waveform-test.js"
6
+ import {TimelineSchemaTest } from "./routines/timeline-setup.js"
4
7
  import {filmstripTest} from "./routines/filmstrip-test.js"
5
- import {transcriberTest} from "./routines/transcriber-test.js"
6
8
  import {setupTranscodeTest} from "./routines/transcode-test.js"
7
- import {Datafile, Omni, VideoPlayer} from "../timeline/index.js"
8
9
 
9
10
  const driver = await Driver.setup({workerUrl: new URL("../driver/driver.worker.bundle.min.js", import.meta.url)})
10
11
  const results = document.querySelector(".results")!
11
12
 
12
13
  const fetchButton = document.querySelector(".fetch")
13
- const importButton = document.querySelector(".import") as HTMLButtonElement
14
-
15
- const playButton = document.querySelector(".play") as HTMLButtonElement
16
- const stopButton = document.querySelector(".stop") as HTMLButtonElement
17
- const seekButton = document.querySelector(".seek") as HTMLButtonElement
14
+ const fileInput = document.querySelector(".file-input") as HTMLInputElement
18
15
 
19
16
  fetchButton?.addEventListener("click", startDemoFetch)
20
- importButton?.addEventListener("click", startDemoImport)
21
-
22
- const omni = new Omni(driver)
23
- const file = await fetch("/assets/temp/gl.mp4")
24
- const buffer = await file.arrayBuffer()
25
- const uint = new Uint8Array(buffer)
26
-
27
- const {videoA} = await omni.load({videoA: Datafile.make(uint)})
28
- const timeline = omni.timeline(o =>
29
- o.sequence(
30
- o.stack(
31
- o.video(videoA, {duration: 5000}),
32
- o.audio(videoA, {duration: 8000})
33
- ),
34
- o.video(videoA, {duration: 7000})
35
- ))
36
-
37
- const player = await VideoPlayer.create(driver, timeline)
38
- document.body.appendChild(player.canvas)
39
-
40
- playButton.addEventListener("click", () => player.play())
41
- stopButton.addEventListener("click", () => player.pause())
42
- seekButton.addEventListener("change", async (e: Event) => {
43
- const target = e.target as HTMLInputElement
44
- await player.seek(+target.value)
45
- })
17
+ fileInput?.addEventListener("input", startDemoImport)
46
18
 
47
19
  waveformTest(driver)
48
20
  // const transcriber = await transcriberTest(driver)
@@ -55,14 +27,22 @@ waveformTest(driver)
55
27
  }
56
28
 
57
29
  // transcoding tests
58
- async function startDemoImport()
30
+ async function startDemoImport(e: Event)
59
31
  {
60
- const [fileHandle] = await window.showOpenFilePicker()
61
- const file = await fileHandle.getFile()
62
- const transcode = setupTranscodeTest(driver, file)
63
- await filmstripTest(file)
64
- run(transcode, fileHandle.name)
65
- // await transcriber.transcribe(file)
32
+ const file = fileInput.files?.[0]
33
+ if(file) {
34
+ const transcode = setupTranscodeTest(driver, file)
35
+ await filmstripTest(file)
36
+ run(transcode, file.name)
37
+ // await transcriber.transcribe(file)
38
+
39
+ const {timeline, omni} = await TimelineSchemaTest(driver, file)
40
+
41
+ playbackTest(driver, timeline, omni)
42
+ exportTest(omni, timeline)
43
+ }
44
+ // const [fileHandle] = await window.showOpenFilePicker()
45
+ // const file = await fileHandle.getFile()
66
46
  }
67
47
 
68
48
  async function startDemoFetch()
@@ -0,0 +1,8 @@
1
+ import {Omni, TimelineFile} from "../../timeline/index.js"
2
+
3
+ export function exportTest(omni: Omni, timeline: TimelineFile) {
4
+ const exportButton = document.querySelector(".export") as HTMLButtonElement
5
+ exportButton!.addEventListener("click", () => {
6
+ omni.render(timeline).then(() => console.log("done"))
7
+ })
8
+ }
@@ -0,0 +1,21 @@
1
+
2
+ import {Driver} from "../../driver/driver.js"
3
+ import {O, Omni, TimelineFile, VideoPlayer} from "../../timeline/index.js"
4
+
5
+ export async function playbackTest(driver: Driver, timeline: TimelineFile, omni: Omni) {
6
+ const playButton = document.querySelector(".play") as HTMLButtonElement
7
+ const stopButton = document.querySelector(".stop") as HTMLButtonElement
8
+ const seekButton = document.querySelector(".seek") as HTMLButtonElement
9
+ const o = new O({project: timeline})
10
+ const player = await VideoPlayer.create(driver, timeline, (hash) => omni.resources.require(hash).url)
11
+ document.body.appendChild(player.canvas)
12
+
13
+ playButton.addEventListener("click", () => player.play())
14
+ stopButton.addEventListener("click", () => player.pause())
15
+ seekButton.addEventListener("change", async (e: Event) => {
16
+ const target = e.target as HTMLInputElement
17
+ await player.seek(+target.value)
18
+ })
19
+
20
+ player.update(o.state.project)
21
+ }
@@ -0,0 +1,24 @@
1
+
2
+ import {Driver} from "../../driver/driver.js"
3
+ import {Datafile, Item, Omni} from "../../timeline/index.js"
4
+
5
+ export async function TimelineSchemaTest(driver: Driver, file: File) {
6
+ const omni = new Omni(driver)
7
+ const {videoA} = await omni.load({videoA: Datafile.make(file)})
8
+ const timeline = omni.timeline(o => {
9
+ const text = o.text("content", {duration: 1000})
10
+ const style = o.textStyle({fill: "green", fontSize: 100})
11
+ o.set<Item.Text>(text.id, {styleId: style.id})
12
+
13
+ return o.sequence(
14
+ o.stack(
15
+ text,
16
+ o.video(videoA, {duration: 3000, start: 3000}),
17
+ o.audio(videoA, {duration: 1000, start: 3000})
18
+ ),
19
+ o.gap(500),
20
+ o.video(videoA, {duration: 7000, start: 5000})
21
+ )})
22
+
23
+ return {timeline, omni}
24
+ }
@@ -15,10 +15,12 @@ export function setupTranscodeTest(driver: Driver, source: DecoderSource) {
15
15
  async onFrame(frame) {
16
16
  const composed = await driver.composite([
17
17
  {
18
+ id: 0,
18
19
  kind: "image",
19
20
  frame
20
21
  },
21
22
  {
23
+ id: 1,
22
24
  kind: "text",
23
25
  content: "omnitool",
24
26
  style: {
@@ -0,0 +1,9 @@
1
+
2
+ import {Comrade} from "@e280/comrade"
3
+ import {setupDriverWork} from "./fns/work.js"
4
+ import {DriverSchematic} from "./fns/schematic.js"
5
+
6
+ export async function driverWorker() {
7
+ await Comrade.worker<DriverSchematic>(setupDriverWork)
8
+ }
9
+
@@ -5,7 +5,7 @@ import {Science, test, expect} from "@e280/science"
5
5
  const workerUrl = new URL("./driver.worker.bundle.js", import.meta.url)
6
6
 
7
7
  export default Science.suite({
8
- "driver hello world": test(async() => {
8
+ "driver hello world": test.skip(async() => {
9
9
  const driver = await Driver.setup({workerUrl})
10
10
  expect(driver.machina.count).is(0)
11
11
  await driver.thread.work.hello()
@@ -2,6 +2,7 @@ import {Comrade, tune, Thread} from "@e280/comrade"
2
2
  import {ALL_FORMATS, Input, type StreamTargetChunk} from "mediabunny"
3
3
 
4
4
  import {Machina} from "./parts/machina.js"
5
+ import {Compositor} from "./parts/compositor.js"
5
6
  import {setupDriverHost} from "./fns/host.js"
6
7
  import {loadDecoderSource} from "./utils/load-decoder-source.js"
7
8
  import {DecoderInput, DriverSchematic, Composition, EncoderInput, DecoderSource} from "./fns/schematic.js"
@@ -18,12 +19,14 @@ export class Driver {
18
19
  workerUrl: options?.workerUrl ?? "/node_modules/@omnimedia/omnitool/x/driver/driver.worker.bundle.min.js",
19
20
  setupHost: setupDriverHost(machina),
20
21
  })
21
- return new this(machina, thread)
22
+ const compositor = await Compositor.setup()
23
+ return new this(machina, thread, compositor)
22
24
  }
23
25
 
24
26
  constructor(
25
27
  public machina: Machina,
26
- public thread: Thread<DriverSchematic>
28
+ public thread: Thread<DriverSchematic>,
29
+ public compositor: Compositor
27
30
  ) {}
28
31
 
29
32
  async hello() {
@@ -37,7 +40,8 @@ export class Driver {
37
40
  })
38
41
 
39
42
  const audioTrack = await input.getPrimaryAudioTrack()
40
- return await audioTrack?.computeDuration()
43
+ if (!audioTrack) throw new Error("primary audio track not found")
44
+ return await audioTrack.computeDuration()
41
45
  }
42
46
 
43
47
  async getVideoDuration(source: DecoderSource) {
@@ -99,26 +103,8 @@ export class Driver {
99
103
  async composite(
100
104
  composition: Composition,
101
105
  ) {
102
- const transfer = this.#collectTransferablesFromComposition(composition)
103
- return await this.thread.work.composite[tune]({transfer})(composition)
106
+ return await this.compositor.composite(composition)
104
107
  }
105
108
 
106
- #collectTransferablesFromComposition(composition: Composition) {
107
- const transferables: Transferable[] = []
108
-
109
- const visit = (node: Composition) => {
110
- if (Array.isArray(node)) {
111
- for (const child of node)
112
- visit(child)
113
- }
114
- else if (node && typeof node === 'object' && 'kind' in node) {
115
- if (node.kind === 'image' && node.frame instanceof VideoFrame)
116
- transferables.push(node.frame)
117
- }
118
- }
119
-
120
- visit(composition)
121
- return transferables
122
- }
123
109
  }
124
110
 
@@ -4,6 +4,7 @@ import {AsSchematic} from "@e280/comrade"
4
4
  import type {AudioEncodingConfig, StreamTargetChunk, VideoEncodingConfig} from "mediabunny"
5
5
 
6
6
  import {Mat6} from "../../timeline/utils/matrix.js"
7
+ import {Id} from "../../timeline/index.js"
7
8
 
8
9
  export type DriverSchematic = AsSchematic<{
9
10
 
@@ -26,8 +27,6 @@ export type DriverSchematic = AsSchematic<{
26
27
  }): Promise<void>
27
28
 
28
29
  encode(input: EncoderInput & {bridge: WritableStream<StreamTargetChunk>}): Promise<void>
29
-
30
- composite(input: Composition): Promise<VideoFrame>
31
30
  }
32
31
 
33
32
  // happens on the main thread
@@ -73,6 +72,7 @@ export interface MuxOpts {
73
72
  export type Composition = Layer | (Layer | Composition)[]
74
73
 
75
74
  export type TextLayer = {
75
+ id: Id
76
76
  kind: 'text'
77
77
  content: string
78
78
  style?: TextStyleOptions
@@ -80,12 +80,14 @@ export type TextLayer = {
80
80
  }
81
81
 
82
82
  export type ImageLayer = {
83
+ id: Id
83
84
  kind: 'image'
84
85
  frame: VideoFrame
85
86
  matrix?: Mat6
86
87
  }
87
88
 
88
89
  export type TransitionLayer = {
90
+ id: Id
89
91
  kind: 'transition'
90
92
  name: string
91
93
  progress: number
@@ -94,10 +96,12 @@ export type TransitionLayer = {
94
96
  }
95
97
 
96
98
  export type GapLayer = {
99
+ id: Id
97
100
  kind: 'gap'
98
101
  }
99
102
 
100
103
  export type Audio = {
104
+ id: Id
101
105
  kind: "audio"
102
106
  data: AudioData
103
107
  }
@@ -1,10 +1,8 @@
1
1
  import {Comrade} from "@e280/comrade"
2
- import {autoDetectRenderer, Container, Renderer, Sprite, Text, Texture, DOMAdapter, WebWorkerAdapter, Matrix} from "pixi.js"
2
+ import {Sprite, Text, Texture, DOMAdapter, WebWorkerAdapter} 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 {Mat6, mat6ToMatrix} from "../../timeline/utils/matrix.js"
6
- import {makeTransition} from "../../features/transition/transition.js"
7
- import {Composition, DecoderSource, DriverSchematic, Layer} from "./schematic.js"
5
+ import {DecoderSource, DriverSchematic} from "./schematic.js"
8
6
 
9
7
  DOMAdapter.set(WebWorkerAdapter)
10
8
 
@@ -111,136 +109,7 @@ export const setupDriverWork = (
111
109
  await Promise.all(promises)
112
110
  await output.finalize()
113
111
  },
114
-
115
- async composite(composition) {
116
- const {stage, renderer} = await renderPIXI(1920, 1080)
117
- stage.removeChildren()
118
-
119
- const {dispose} = await renderLayer(composition, stage)
120
- renderer.render(stage)
121
-
122
- // make sure browser support webgl/webgpu otherwise it might take much longer to construct frame
123
- // if its very slow on eg edge try chrome
124
- const frame = new VideoFrame(renderer.canvas, {
125
- timestamp: 0,
126
- duration: 0,
127
- })
128
-
129
- renderer.clear()
130
- dispose()
131
-
132
- shell.transfer = [frame]
133
- return frame
134
- }
135
112
  }))
136
113
  )
137
114
 
138
- // TODO suspicious global, probably bad
139
- let pixi: {
140
- renderer: Renderer
141
- stage: Container
142
- } | null = null
143
-
144
- async function renderPIXI(width: number, height: number) {
145
- if (pixi)
146
- return pixi
147
-
148
- const renderer = await autoDetectRenderer({
149
- width,
150
- height,
151
- preference: "webgl", // webgl and webgl2 causes memory leaks on chrome
152
- background: "black",
153
- preferWebGLVersion: 2
154
- })
155
-
156
- const stage = new Container()
157
- pixi = {renderer, stage}
158
-
159
- return pixi
160
- }
161
-
162
- const transitions: Map<string, ReturnType<typeof makeTransition>> = new Map()
163
-
164
115
  type RenderableObject = Sprite | Text | Texture
165
-
166
- async function renderLayer(
167
- layer: Layer | Composition,
168
- parent: Container,
169
- ) {
170
- if (Array.isArray(layer)) {
171
- layer.reverse()
172
- const disposers: (() => void)[] = []
173
- for (const child of layer) {
174
- const result = await renderLayer(child, parent)
175
- disposers.push(result.dispose)
176
- }
177
- return {dispose: () => disposers.forEach(d => d())}
178
- }
179
-
180
- switch (layer.kind) {
181
- case 'text':
182
- return renderTextLayer(layer, parent)
183
- case 'image':
184
- return renderImageLayer(layer, parent)
185
- case 'transition':
186
- return renderTransitionLayer(layer, parent)
187
- case 'gap': {
188
- pixi?.renderer.clear()
189
- return {dispose: () => {}}
190
- }
191
- default:
192
- console.warn('Unknown layer kind', (layer as any).kind)
193
- return {dispose: () => {}}
194
- }
195
- }
196
-
197
- function renderTextLayer(
198
- layer: Extract<Layer, {kind: 'text'}>,
199
- parent: Container,
200
- ) {
201
- const text = new Text({
202
- text: layer.content,
203
- style: layer.style
204
- })
205
- applyTransform(text, layer.matrix)
206
- parent.addChild(text)
207
- return {dispose: () => text.destroy(true)}
208
- }
209
-
210
- function renderImageLayer(
211
- layer: Extract<Layer, {kind: 'image'}>,
212
- parent: Container,
213
- ) {
214
- const texture = Texture.from(layer.frame)
215
- const sprite = new Sprite(texture)
216
- applyTransform(sprite, layer.matrix)
217
- parent.addChild(sprite)
218
- return {dispose: () => {
219
- sprite.destroy(true)
220
- texture.destroy(true)
221
- layer.frame.close()
222
- }}
223
- }
224
-
225
- function renderTransitionLayer(
226
- {from, to, progress, name}: Extract<Layer, {kind: 'transition'}>,
227
- parent: Container,
228
- ) {
229
- const transition = transitions.get(name) ??
230
- (transitions.set(name, makeTransition({
231
- name: "circle",
232
- renderer: pixi!.renderer
233
- })),
234
- transitions.get(name)!
235
- )
236
- const texture = transition.render({from, to, progress, width: from.displayWidth, height: from.displayHeight})
237
- const sprite = new Sprite(texture)
238
- parent.addChild(sprite)
239
- return {dispose: () => sprite.destroy(false)}
240
- }
241
-
242
- function applyTransform(target: Sprite | Text, worldMatrix?: Mat6) {
243
- if (!worldMatrix) return
244
- const mx = mat6ToMatrix(worldMatrix)
245
- target.setFromMatrix(mx)
246
- }
@@ -0,0 +1,178 @@
1
+ import {autoDetectRenderer, Container, Renderer, Sprite, Text, Texture} from "pixi.js"
2
+
3
+ import {Composition, Layer} from "../fns/schematic.js"
4
+ import {Mat6, mat6ToMatrix} from "../../timeline/utils/matrix.js"
5
+ import {makeTransition} from "../../features/transition/transition.js"
6
+
7
+ export class Compositor {
8
+
9
+ static async setup() {
10
+ const renderer = await autoDetectRenderer({
11
+ width: 1920,
12
+ height: 1080,
13
+ preference: "webgl", // webgl and webgl2 causes memory leaks on chrome
14
+ background: "black",
15
+ preferWebGLVersion: 2
16
+ })
17
+ const stage = new Container()
18
+ stage.interactive = true
19
+ return new this({renderer, stage})
20
+ }
21
+
22
+ constructor(public pixi: {renderer: Renderer, stage: Container}) {}
23
+
24
+ #transitions: Map<string, ReturnType<typeof makeTransition>> = new Map()
25
+ #objects = new Map<number, Container>()
26
+
27
+ async composite(
28
+ composition: Composition,
29
+ ) {
30
+ const {stage, renderer} = this.pixi
31
+
32
+ this.#cleanup(this.#collectIds(composition))
33
+ const {dispose} = await this.#renderLayer(composition, stage)
34
+ renderer.render(stage)
35
+
36
+ // make sure browser support webgl/webgpu otherwise it might take much longer to construct frame
37
+ // if its very slow on eg edge try chrome
38
+ const frame = new VideoFrame(renderer.canvas, {
39
+ timestamp: 0,
40
+ duration: 0,
41
+ })
42
+
43
+ dispose()
44
+
45
+ return frame
46
+ }
47
+
48
+ async #renderLayer(
49
+ layer: Layer | Composition,
50
+ parent: Container,
51
+ ) {
52
+ if (Array.isArray(layer)) {
53
+ layer.reverse()
54
+ const disposers: (() => void)[] = []
55
+ for (const child of layer) {
56
+ const result = await this.#renderLayer(child, parent)
57
+ disposers.push(result.dispose)
58
+ }
59
+ return {dispose: () => disposers.forEach(d => d())}
60
+ }
61
+
62
+ switch (layer.kind) {
63
+ case 'text':
64
+ return this.#renderTextLayer(layer, parent)
65
+ case 'image':
66
+ return this.#renderImageLayer(layer, parent)
67
+ case 'transition':
68
+ return this.#renderTransitionLayer(layer, parent)
69
+ case 'gap': {
70
+ this.pixi?.renderer.clear()
71
+ return {dispose: () => {}}
72
+ }
73
+ default:
74
+ console.warn('Unknown layer kind', (layer as any).kind)
75
+ return {dispose: () => {}}
76
+ }
77
+ }
78
+
79
+ #renderTextLayer(
80
+ layer: Extract<Layer, {kind: 'text'}>,
81
+ parent: Container,
82
+ ) {
83
+ const text = this.#findOrCreate<Text>(layer)!
84
+ this.#applyTransform(text, layer.matrix)
85
+ parent.addChild(text)
86
+ return {
87
+ dispose: () => {}
88
+ }
89
+ }
90
+
91
+ #renderImageLayer(
92
+ layer: Extract<Layer, {kind: 'image'}>,
93
+ parent: Container,
94
+ ) {
95
+ const texture = Texture.from(layer.frame)
96
+ const sprite = this.#findOrCreate<Sprite>(layer)!
97
+ sprite.texture = texture
98
+ this.#applyTransform(sprite, layer.matrix)
99
+ parent.addChild(sprite)
100
+ return {
101
+ dispose: () => {
102
+ texture.destroy(true)
103
+ layer.frame.close()
104
+ }
105
+ }
106
+ }
107
+
108
+ #renderTransitionLayer(
109
+ {from, to, progress, name}: Extract<Layer, {kind: 'transition'}>,
110
+ parent: Container,
111
+ ) {
112
+ const transition = this.#transitions.get(name) ??
113
+ (this.#transitions.set(name, makeTransition({
114
+ name: "circle",
115
+ renderer: this.pixi.renderer
116
+ })),
117
+ this.#transitions.get(name)!
118
+ )
119
+ const texture = transition.render({from, to, progress, width: from.displayWidth, height: from.displayHeight})
120
+ const sprite = new Sprite(texture)
121
+ parent.addChild(sprite)
122
+ return {dispose: () => sprite.destroy(false)}
123
+ }
124
+
125
+ #applyTransform(target: Sprite | Text, worldMatrix?: Mat6) {
126
+ if (!worldMatrix) return
127
+ const mx = mat6ToMatrix(worldMatrix)
128
+ target.setFromMatrix(mx)
129
+ }
130
+
131
+ #findOrCreate<T = Container>(layer: Layer) {
132
+ const object = this.#objects.get(layer.id)
133
+ if(!object) {
134
+ switch (layer.kind) {
135
+ case 'text': {
136
+ const text = new Text({
137
+ text: layer.content,
138
+ style: layer.style
139
+ })
140
+ text.onmouseenter = () => console.log("enter text")
141
+ return this.#objects
142
+ .set(layer.id, text)
143
+ .get(layer.id) as T
144
+ }
145
+ case 'image': {
146
+ const sprite = new Sprite()
147
+ sprite.onmouseenter = () => console.log("enter")
148
+ return this.#objects
149
+ .set(layer.id, sprite)
150
+ .get(layer.id) as T
151
+ }
152
+ }
153
+ } else return object as T
154
+ }
155
+
156
+ #collectIds(layers: Layer | Composition): Set<number> {
157
+ const result = new Set<number>()
158
+ const traverse = (node: Layer | Composition) => {
159
+ if (Array.isArray(node)) {
160
+ for (const child of node) traverse(child)
161
+ } else {
162
+ result.add(node.id)
163
+ }
164
+ }
165
+ traverse(layers)
166
+ return result
167
+ }
168
+
169
+ #cleanup(activeIds: Set<number>) {
170
+ for (const id of this.#objects.keys()) {
171
+ if (!activeIds.has(id)) {
172
+ const obj = this.#objects.get(id)!
173
+ obj.destroy(true)
174
+ this.#objects.delete(id)
175
+ }
176
+ }
177
+ }
178
+ }