@omnimedia/omnitool 1.0.0 → 1.1.0-10
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/LICENSE +21 -0
- package/README.md +120 -2
- package/package.json +59 -27
- package/s/_archive/types.ts +107 -0
- package/s/context.ts +7 -0
- package/s/demo/demo.bundle.ts +68 -0
- package/s/demo/demo.css +59 -0
- package/s/demo/routines/filmstrip-test.ts +68 -0
- package/s/demo/routines/load-video.ts +7 -0
- package/s/demo/routines/transcode-test.ts +46 -0
- package/s/demo/routines/transcriber-test.ts +34 -0
- package/s/demo/routines/transitions-test.ts +43 -0
- package/s/demo/routines/waveform-test.ts +12 -0
- package/s/driver/driver.test.ts +15 -0
- package/s/driver/driver.ts +124 -0
- package/s/driver/driver.worker.bundle.ts +7 -0
- package/s/driver/fns/host.ts +13 -0
- package/s/driver/fns/schematic.ts +106 -0
- package/s/driver/fns/work.ts +249 -0
- package/s/driver/parts/constants.ts +17 -0
- package/s/driver/parts/machina.ts +27 -0
- package/s/driver/utils/load-decoder-source.ts +12 -0
- package/s/driver/utils/sleep.ts +3 -0
- package/s/features/speech/transcribe/default-spec.ts +11 -0
- package/s/features/speech/transcribe/parts/load-pipe.ts +19 -0
- package/s/features/speech/transcribe/parts/prep-audio.ts +23 -0
- package/s/features/speech/transcribe/parts/transcribe.ts +70 -0
- package/s/features/speech/transcribe/transcriber.ts +46 -0
- package/s/features/speech/transcribe/types.ts +82 -0
- package/s/features/speech/transcribe/worker.bundle.ts +40 -0
- package/s/features/transition/parts/fragment.ts +24 -0
- package/s/features/transition/parts/types.ts +94 -0
- package/s/features/transition/parts/uniforms.ts +29 -0
- package/s/features/transition/parts/vertex.ts +31 -0
- package/s/features/transition/transition.ts +60 -0
- package/s/index.html.ts +58 -0
- package/s/index.ts +2 -39
- package/s/tests.test.ts +8 -0
- package/s/timeline/index.ts +15 -0
- package/s/timeline/parts/basics.ts +17 -0
- 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/filmstrip.ts +159 -0
- package/s/timeline/parts/item.ts +90 -0
- package/s/timeline/parts/media.ts +35 -0
- package/s/timeline/parts/resource-pool.ts +27 -0
- package/s/timeline/parts/resource.ts +11 -0
- package/s/timeline/parts/waveform.ts +62 -0
- package/s/timeline/sugar/builders.ts +102 -0
- package/s/timeline/sugar/o.ts +150 -0
- package/s/timeline/sugar/omni-test.ts +38 -0
- package/s/timeline/sugar/omni.ts +40 -0
- package/s/timeline/types.ts +29 -0
- package/s/timeline/utils/audio-stream.ts +15 -0
- package/s/timeline/utils/checksum.ts +20 -0
- package/s/timeline/utils/datafile.ts +21 -0
- package/s/timeline/utils/dummy-data.ts +7 -0
- package/s/timeline/utils/matrix.ts +33 -0
- package/s/timeline/utils/video-cursor.ts +40 -0
- package/s/tools/common/loader.ts +26 -0
- package/s/tools/common/transformer-pipeline.ts +26 -0
- package/s/tools/speech-recognition/common/model.ts +26 -0
- package/s/tools/speech-recognition/whisper/fns/host.ts +25 -0
- package/s/tools/speech-recognition/whisper/fns/schematic.ts +23 -0
- package/s/tools/speech-recognition/whisper/fns/work.ts +91 -0
- package/s/tools/speech-recognition/whisper/parts/types.ts +38 -0
- package/s/tools/speech-recognition/whisper/parts/worker.bundle.ts +7 -0
- package/s/tools/speech-recognition/whisper/tool.ts +70 -0
- package/x/context.d.ts +4 -0
- package/x/context.js +6 -0
- package/x/context.js.map +1 -0
- package/x/demo/demo.bundle.d.ts +1 -0
- package/x/demo/demo.bundle.js +55 -0
- package/x/demo/demo.bundle.js.map +1 -0
- package/x/demo/demo.bundle.min.js +120 -0
- package/x/demo/demo.bundle.min.js.map +7 -0
- package/x/demo/demo.css +59 -0
- package/x/demo/routines/filmstrip-test.d.ts +1 -0
- package/x/demo/routines/filmstrip-test.js +62 -0
- package/x/demo/routines/filmstrip-test.js.map +1 -0
- package/x/demo/routines/load-video.d.ts +1 -0
- package/x/demo/routines/load-video.js +6 -0
- package/x/demo/routines/load-video.js.map +1 -0
- package/x/demo/routines/transcode-test.d.ts +6 -0
- package/x/demo/routines/transcode-test.js +40 -0
- package/x/demo/routines/transcode-test.js.map +1 -0
- package/x/demo/routines/transcriber-test.d.ts +4 -0
- package/x/demo/routines/transcriber-test.js +33 -0
- package/x/demo/routines/transcriber-test.js.map +1 -0
- package/x/demo/routines/transitions-test.d.ts +5 -0
- package/x/demo/routines/transitions-test.js +35 -0
- package/x/demo/routines/transitions-test.js.map +1 -0
- package/x/demo/routines/waveform-test.d.ts +1 -0
- package/x/demo/routines/waveform-test.js +11 -0
- package/x/demo/routines/waveform-test.js.map +1 -0
- package/x/driver/driver.d.ts +20 -0
- package/x/driver/driver.js +104 -0
- package/x/driver/driver.js.map +1 -0
- package/x/driver/driver.test.d.ts +5 -0
- package/x/driver/driver.test.js +12 -0
- package/x/driver/driver.test.js.map +1 -0
- package/x/driver/driver.worker.bundle.d.ts +1 -0
- package/x/driver/driver.worker.bundle.js +4 -0
- package/x/driver/driver.worker.bundle.js.map +1 -0
- package/x/driver/driver.worker.bundle.min.js +3537 -0
- package/x/driver/driver.worker.bundle.min.js.map +7 -0
- package/x/driver/fns/host.d.ts +25 -0
- package/x/driver/fns/host.js +7 -0
- package/x/driver/fns/host.js.map +1 -0
- package/x/driver/fns/schematic.d.ts +84 -0
- package/x/driver/fns/schematic.js +2 -0
- package/x/driver/fns/schematic.js.map +1 -0
- package/x/driver/fns/work.d.ts +26 -0
- package/x/driver/fns/work.js +201 -0
- package/x/driver/fns/work.js.map +1 -0
- package/x/driver/parts/constants.d.ts +2 -0
- package/x/driver/parts/constants.js +17 -0
- package/x/driver/parts/constants.js.map +1 -0
- package/x/driver/parts/machina.d.ts +23 -0
- package/x/driver/parts/machina.js +14 -0
- package/x/driver/parts/machina.js.map +1 -0
- package/x/driver/utils/load-decoder-source.d.ts +3 -0
- package/x/driver/utils/load-decoder-source.js +11 -0
- package/x/driver/utils/load-decoder-source.js.map +1 -0
- package/x/driver/utils/sleep.d.ts +1 -0
- package/x/driver/utils/sleep.js +4 -0
- package/x/driver/utils/sleep.js.map +1 -0
- package/x/features/speech/transcribe/default-spec.d.ts +2 -0
- package/x/features/speech/transcribe/default-spec.js +8 -0
- package/x/features/speech/transcribe/default-spec.js.map +1 -0
- package/x/features/speech/transcribe/parts/load-pipe.d.ts +2 -0
- package/x/features/speech/transcribe/parts/load-pipe.js +13 -0
- package/x/features/speech/transcribe/parts/load-pipe.js.map +1 -0
- package/x/features/speech/transcribe/parts/prep-audio.d.ts +5 -0
- package/x/features/speech/transcribe/parts/prep-audio.js +21 -0
- package/x/features/speech/transcribe/parts/prep-audio.js.map +1 -0
- package/x/features/speech/transcribe/parts/transcribe.d.ts +5 -0
- package/x/features/speech/transcribe/parts/transcribe.js +56 -0
- package/x/features/speech/transcribe/parts/transcribe.js.map +1 -0
- package/x/features/speech/transcribe/transcriber.d.ts +5 -0
- package/x/features/speech/transcribe/transcriber.js +33 -0
- package/x/features/speech/transcribe/transcriber.js.map +1 -0
- package/x/features/speech/transcribe/types.d.ts +66 -0
- package/x/features/speech/transcribe/types.js.map +1 -0
- package/x/features/speech/transcribe/worker.bundle.d.ts +1 -0
- package/x/features/speech/transcribe/worker.bundle.js +33 -0
- package/x/features/speech/transcribe/worker.bundle.js.map +1 -0
- package/x/features/speech/transcribe/worker.bundle.min.js +2916 -0
- package/x/features/speech/transcribe/worker.bundle.min.js.map +7 -0
- package/x/features/transition/parts/fragment.d.ts +1 -0
- package/x/features/transition/parts/fragment.js +25 -0
- package/x/features/transition/parts/fragment.js.map +1 -0
- package/x/features/transition/parts/types.d.ts +23 -0
- package/x/features/transition/parts/types.js +2 -0
- package/x/features/transition/parts/types.js.map +1 -0
- package/x/features/transition/parts/uniforms.d.ts +31 -0
- package/x/features/transition/parts/uniforms.js +27 -0
- package/x/features/transition/parts/uniforms.js.map +1 -0
- package/x/features/transition/parts/vertex.d.ts +1 -0
- package/x/features/transition/parts/vertex.js +32 -0
- package/x/features/transition/parts/vertex.js.map +1 -0
- package/x/features/transition/transition.d.ts +5 -0
- package/x/features/transition/transition.js +50 -0
- package/x/features/transition/transition.js.map +1 -0
- package/x/index.d.ts +2 -9
- package/x/index.html +115 -0
- package/x/index.html.d.ts +2 -0
- package/x/index.html.js +52 -0
- package/x/index.html.js.map +1 -0
- package/x/index.js +2 -29
- package/x/index.js.map +1 -1
- package/x/tests.test.d.ts +1 -0
- package/x/tests.test.js +6 -0
- package/x/tests.test.js.map +1 -0
- package/x/timeline/index.d.ts +11 -0
- package/x/timeline/index.js +12 -0
- package/x/timeline/index.js.map +1 -0
- package/x/timeline/parts/basics.d.ts +12 -0
- package/x/timeline/parts/basics.js +2 -0
- package/x/timeline/parts/basics.js.map +1 -0
- 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/filmstrip.d.ts +39 -0
- package/x/timeline/parts/filmstrip.js +117 -0
- package/x/timeline/parts/filmstrip.js.map +1 -0
- package/x/timeline/parts/item.d.ts +68 -0
- package/x/timeline/parts/item.js +16 -0
- package/x/timeline/parts/item.js.map +1 -0
- package/x/timeline/parts/media.d.ts +10 -0
- package/x/timeline/parts/media.js +30 -0
- package/x/timeline/parts/media.js.map +1 -0
- package/x/timeline/parts/resource-pool.d.ts +7 -0
- package/x/timeline/parts/resource-pool.js +19 -0
- package/x/timeline/parts/resource-pool.js.map +1 -0
- package/x/timeline/parts/resource.d.ts +8 -0
- package/x/timeline/parts/resource.js +2 -0
- package/x/timeline/parts/resource.js.map +1 -0
- package/x/timeline/parts/waveform.d.ts +8 -0
- package/x/timeline/parts/waveform.js +51 -0
- package/x/timeline/parts/waveform.js.map +1 -0
- package/x/timeline/sugar/builders.d.ts +1 -0
- package/x/timeline/sugar/builders.js +104 -0
- package/x/timeline/sugar/builders.js.map +1 -0
- package/x/timeline/sugar/o.d.ts +32 -0
- package/x/timeline/sugar/o.js +114 -0
- package/x/timeline/sugar/o.js.map +1 -0
- package/x/timeline/sugar/omni-test.d.ts +1 -0
- package/x/timeline/sugar/omni-test.js +22 -0
- package/x/timeline/sugar/omni-test.js.map +1 -0
- package/x/timeline/sugar/omni.d.ts +14 -0
- package/x/timeline/sugar/omni.js +28 -0
- package/x/timeline/sugar/omni.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/checksum.d.ts +8 -0
- package/x/timeline/utils/checksum.js +21 -0
- package/x/timeline/utils/checksum.js.map +1 -0
- package/x/timeline/utils/datafile.d.ts +9 -0
- package/x/timeline/utils/datafile.js +20 -0
- package/x/timeline/utils/datafile.js.map +1 -0
- package/x/timeline/utils/dummy-data.d.ts +2 -0
- package/x/timeline/utils/dummy-data.js +3 -0
- package/x/timeline/utils/dummy-data.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/common/loader.d.ts +19 -0
- package/x/tools/common/loader.js +18 -0
- package/x/tools/common/loader.js.map +1 -0
- package/x/tools/common/transformer-pipeline.d.ts +8 -0
- package/x/tools/common/transformer-pipeline.js +24 -0
- package/x/tools/common/transformer-pipeline.js.map +1 -0
- package/x/tools/speech-recognition/common/model.d.ts +14 -0
- package/x/tools/speech-recognition/common/model.js +16 -0
- package/x/tools/speech-recognition/common/model.js.map +1 -0
- package/x/tools/speech-recognition/whisper/fns/host.d.ts +13 -0
- package/x/tools/speech-recognition/whisper/fns/host.js +19 -0
- package/x/tools/speech-recognition/whisper/fns/host.js.map +1 -0
- package/x/tools/speech-recognition/whisper/fns/schematic.d.ts +19 -0
- package/x/tools/speech-recognition/whisper/fns/schematic.js +2 -0
- package/x/tools/speech-recognition/whisper/fns/schematic.js.map +1 -0
- package/x/tools/speech-recognition/whisper/fns/work.d.ts +12 -0
- package/x/tools/speech-recognition/whisper/fns/work.js +74 -0
- package/x/tools/speech-recognition/whisper/fns/work.js.map +1 -0
- package/x/tools/speech-recognition/whisper/parts/types.d.ts +31 -0
- package/x/tools/speech-recognition/whisper/parts/types.js +2 -0
- package/x/tools/speech-recognition/whisper/parts/types.js.map +1 -0
- package/x/tools/speech-recognition/whisper/parts/worker.bundle.d.ts +1 -0
- package/x/tools/speech-recognition/whisper/parts/worker.bundle.js +4 -0
- package/x/tools/speech-recognition/whisper/parts/worker.bundle.js.map +1 -0
- package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js +8 -0
- package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js.map +7 -0
- package/x/tools/speech-recognition/whisper/tool.d.ts +12 -0
- package/x/tools/speech-recognition/whisper/tool.js +63 -0
- package/x/tools/speech-recognition/whisper/tool.js.map +1 -0
- package/s/parts/compositor.ts +0 -5
- package/s/parts/export.ts +0 -5
- package/s/parts/video-decoder.ts +0 -27
- package/s/parts/video-encoder.ts +0 -15
- package/s/tools/generate-id.ts +0 -7
- package/s/tools/mp4boxjs/LICENSE.md +0 -24
- package/s/tools/mp4boxjs/demuxer.ts +0 -106
- package/s/tools/mp4boxjs/mp4box.adapter.ts +0 -148
- package/s/tools/mp4boxjs/mp4box.js +0 -8206
- package/s/types.ts +0 -10
- package/x/parts/compositor.d.ts +0 -4
- package/x/parts/compositor.js +0 -5
- package/x/parts/compositor.js.map +0 -1
- package/x/parts/export.d.ts +0 -7
- package/x/parts/export.js +0 -5
- package/x/parts/export.js.map +0 -1
- package/x/parts/video-decoder.d.ts +0 -8
- package/x/parts/video-decoder.js +0 -20
- package/x/parts/video-decoder.js.map +0 -1
- package/x/parts/video-encoder.d.ts +0 -6
- package/x/parts/video-encoder.js +0 -12
- package/x/parts/video-encoder.js.map +0 -1
- package/x/tools/generate-id.d.ts +0 -1
- package/x/tools/generate-id.js +0 -8
- package/x/tools/generate-id.js.map +0 -1
- package/x/tools/mp4boxjs/demuxer.d.ts +0 -24
- package/x/tools/mp4boxjs/demuxer.js +0 -88
- package/x/tools/mp4boxjs/demuxer.js.map +0 -1
- package/x/tools/mp4boxjs/mp4box.adapter.d.ts +0 -128
- package/x/tools/mp4boxjs/mp4box.adapter.js +0 -11
- package/x/tools/mp4boxjs/mp4box.adapter.js.map +0 -1
- package/x/types.d.ts +0 -7
- package/x/types.js.map +0 -1
- /package/x/{types.js → features/speech/transcribe/types.js} +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import {Driver} from "./driver.js"
|
|
3
|
+
import {Science, test, expect} from "@e280/science"
|
|
4
|
+
|
|
5
|
+
const workerUrl = new URL("./driver.worker.bundle.js", import.meta.url)
|
|
6
|
+
|
|
7
|
+
export default Science.suite({
|
|
8
|
+
"driver hello world": test(async() => {
|
|
9
|
+
const driver = await Driver.setup({workerUrl})
|
|
10
|
+
expect(driver.machina.count).is(0)
|
|
11
|
+
await driver.thread.work.hello()
|
|
12
|
+
expect(driver.machina.count).is(1)
|
|
13
|
+
}),
|
|
14
|
+
})
|
|
15
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {Comrade, tune, Thread} from "@e280/comrade"
|
|
2
|
+
import {ALL_FORMATS, Input, type StreamTargetChunk} from "mediabunny"
|
|
3
|
+
|
|
4
|
+
import {Machina} from "./parts/machina.js"
|
|
5
|
+
import {setupDriverHost} from "./fns/host.js"
|
|
6
|
+
import {loadDecoderSource} from "./utils/load-decoder-source.js"
|
|
7
|
+
import {DecoderInput, DriverSchematic, Composition, EncoderInput, DecoderSource} from "./fns/schematic.js"
|
|
8
|
+
|
|
9
|
+
export type DriverOptions = {
|
|
10
|
+
workerUrl: URL | string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class Driver {
|
|
14
|
+
static async setup(options: DriverOptions) {
|
|
15
|
+
const machina = new Machina()
|
|
16
|
+
const thread = await Comrade.thread<DriverSchematic>({
|
|
17
|
+
label: "OmnitoolDriver",
|
|
18
|
+
workerUrl: options.workerUrl,
|
|
19
|
+
setupHost: setupDriverHost(machina),
|
|
20
|
+
})
|
|
21
|
+
return new this(machina, thread)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
public machina: Machina,
|
|
26
|
+
public thread: Thread<DriverSchematic>
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
async hello() {
|
|
30
|
+
return this.thread.work.hello()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getAudioDuration(source: DecoderSource) {
|
|
34
|
+
const input = new Input({
|
|
35
|
+
source: await loadDecoderSource(source),
|
|
36
|
+
formats: ALL_FORMATS
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const audioTrack = await input.getPrimaryAudioTrack()
|
|
40
|
+
return await audioTrack?.computeDuration()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async getVideoDuration(source: DecoderSource) {
|
|
44
|
+
const input = new Input({
|
|
45
|
+
source: await loadDecoderSource(source),
|
|
46
|
+
formats: ALL_FORMATS
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const videoTrack = await input.getPrimaryVideoTrack()
|
|
50
|
+
return await videoTrack?.computeDuration()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
decodeVideo(input: DecoderInput) {
|
|
54
|
+
let lastFrame: VideoFrame | null = null
|
|
55
|
+
const videoTransform = new TransformStream<VideoFrame, VideoFrame>({
|
|
56
|
+
async transform(chunk, controller) {
|
|
57
|
+
const frame = await input.onFrame?.(chunk) ?? chunk
|
|
58
|
+
// below code is to prevent mem leaks and hardware accelerated decoder stall
|
|
59
|
+
lastFrame?.close()
|
|
60
|
+
controller.enqueue(frame)
|
|
61
|
+
lastFrame = frame
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
this.thread.work.decodeVideo[tune]({transfer: [videoTransform.writable]})({
|
|
65
|
+
source: input.source,
|
|
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,
|
|
77
|
+
audio: audioTransform.writable,
|
|
78
|
+
start: input.start,
|
|
79
|
+
end: input.end
|
|
80
|
+
})
|
|
81
|
+
return audioTransform.readable
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async encode({video, audio, config}: EncoderInput) {
|
|
85
|
+
const handle = await window.showSaveFilePicker()
|
|
86
|
+
const writable = await handle.createWritable()
|
|
87
|
+
// making bridge because file picker writable is not transferable
|
|
88
|
+
const bridge = new WritableStream<StreamTargetChunk>({
|
|
89
|
+
async write(chunk) {
|
|
90
|
+
await writable.write(chunk)
|
|
91
|
+
},
|
|
92
|
+
async close() {
|
|
93
|
+
await writable.close()
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
return await this.thread.work.encode[tune]({transfer: [audio ?? [], video ?? [], bridge]})({video, audio, config, bridge})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async composite(
|
|
100
|
+
composition: Composition,
|
|
101
|
+
) {
|
|
102
|
+
const transfer = this.#collectTransferablesFromComposition(composition)
|
|
103
|
+
return await this.thread.work.composite[tune]({transfer})(composition)
|
|
104
|
+
}
|
|
105
|
+
|
|
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
|
+
}
|
|
124
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
import {Comrade} from "@e280/comrade"
|
|
3
|
+
import {Machina} from "../parts/machina.js"
|
|
4
|
+
import {DriverSchematic} from "./schematic.js"
|
|
5
|
+
|
|
6
|
+
export const setupDriverHost = (machina: Machina) => (
|
|
7
|
+
Comrade.host<DriverSchematic>(_shell => ({
|
|
8
|
+
async world() {
|
|
9
|
+
machina.count++
|
|
10
|
+
},
|
|
11
|
+
}))
|
|
12
|
+
)
|
|
13
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
|
|
2
|
+
import {AsSchematic} from "@e280/comrade"
|
|
3
|
+
import type {AudioEncodingConfig, StreamTargetChunk, VideoEncodingConfig} from "mediabunny"
|
|
4
|
+
|
|
5
|
+
import {Mat6} from "../../timeline/utils/matrix.js"
|
|
6
|
+
|
|
7
|
+
export type DriverSchematic = AsSchematic<{
|
|
8
|
+
|
|
9
|
+
// happens on the web worker
|
|
10
|
+
work: {
|
|
11
|
+
hello(): Promise<void>
|
|
12
|
+
|
|
13
|
+
decodeAudio(input: {
|
|
14
|
+
source: DecoderSource
|
|
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
|
|
25
|
+
}): Promise<void>
|
|
26
|
+
|
|
27
|
+
encode(input: EncoderInput & {bridge: WritableStream<StreamTargetChunk>}): Promise<void>
|
|
28
|
+
|
|
29
|
+
composite(input: Composition): Promise<VideoFrame>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// happens on the main thread
|
|
33
|
+
host: {
|
|
34
|
+
world(): Promise<void>
|
|
35
|
+
}
|
|
36
|
+
}>
|
|
37
|
+
|
|
38
|
+
export interface EncoderInput {
|
|
39
|
+
video?: ReadableStream<VideoFrame>
|
|
40
|
+
audio?: ReadableStream<AudioData>
|
|
41
|
+
config: RenderConfig
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RenderConfig {
|
|
45
|
+
video: VideoEncodingConfig
|
|
46
|
+
audio: AudioEncodingConfig
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type DecoderSource = Blob | string | URL
|
|
50
|
+
|
|
51
|
+
export interface DecoderInput {
|
|
52
|
+
source: DecoderSource
|
|
53
|
+
start?: number
|
|
54
|
+
end?: number
|
|
55
|
+
onFrame?: (frame: VideoFrame) => Promise<VideoFrame>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface MuxOpts {
|
|
59
|
+
config: {
|
|
60
|
+
video: {
|
|
61
|
+
width: number
|
|
62
|
+
height: number
|
|
63
|
+
},
|
|
64
|
+
audio?: {
|
|
65
|
+
codec: "opus" | "aac"
|
|
66
|
+
numberOfChannels: number
|
|
67
|
+
sampleRate: number
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type Composition = Layer | (Layer | Composition)[]
|
|
73
|
+
|
|
74
|
+
export type TextLayer = {
|
|
75
|
+
kind: 'text'
|
|
76
|
+
content: string
|
|
77
|
+
fontSize?: number
|
|
78
|
+
color?: string
|
|
79
|
+
matrix?: Mat6
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type ImageLayer = {
|
|
83
|
+
kind: 'image'
|
|
84
|
+
frame: VideoFrame
|
|
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
|
+
}
|
|
104
|
+
|
|
105
|
+
export type Layer = TextLayer | ImageLayer | TransitionLayer | GapLayer
|
|
106
|
+
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {Comrade} from "@e280/comrade"
|
|
2
|
+
import {autoDetectRenderer, Container, Renderer, Sprite, Text, Texture, DOMAdapter, WebWorkerAdapter, Matrix} from "pixi.js"
|
|
3
|
+
import {Input, ALL_FORMATS, VideoSampleSink, Output, Mp4OutputFormat, VideoSampleSource, VideoSample, AudioSampleSink, AudioSampleSource, AudioSample, StreamTarget, BlobSource, UrlSource} from "mediabunny"
|
|
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"
|
|
8
|
+
|
|
9
|
+
DOMAdapter.set(WebWorkerAdapter)
|
|
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
|
+
|
|
19
|
+
export const setupDriverWork = (
|
|
20
|
+
Comrade.work<DriverSchematic>(shell => ({
|
|
21
|
+
async hello() {
|
|
22
|
+
await shell.host.world()
|
|
23
|
+
},
|
|
24
|
+
|
|
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()
|
|
42
|
+
}
|
|
43
|
+
await audioWriter.close()
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async decodeVideo({source, video, start, end}) {
|
|
48
|
+
const input = new Input({
|
|
49
|
+
source: await loadSource(source),
|
|
50
|
+
formats: ALL_FORMATS
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const videoTrack = await input.getPrimaryVideoTrack()
|
|
54
|
+
const videoDecodable = await videoTrack?.canDecode()
|
|
55
|
+
const videoWriter = video.getWriter()
|
|
56
|
+
|
|
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
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async encode({video, audio, config, bridge}) {
|
|
70
|
+
const output = new Output({
|
|
71
|
+
format: new Mp4OutputFormat(),
|
|
72
|
+
target: new StreamTarget(bridge, {chunked: true})
|
|
73
|
+
})
|
|
74
|
+
// since AudioSample is not transferable it fails to transfer encoder bitrate config
|
|
75
|
+
// so it needs to be hardcoded not set through constants eg QUALITY_LOW
|
|
76
|
+
|
|
77
|
+
const promises = []
|
|
78
|
+
|
|
79
|
+
if(video) {
|
|
80
|
+
const videoSource = new VideoSampleSource(config.video)
|
|
81
|
+
output.addVideoTrack(videoSource)
|
|
82
|
+
const videoReader = video.getReader()
|
|
83
|
+
promises.push((async () => {
|
|
84
|
+
while (true) {
|
|
85
|
+
const {done, value} = await videoReader.read()
|
|
86
|
+
if (done) break
|
|
87
|
+
const sample = new VideoSample(value)
|
|
88
|
+
await videoSource.add(sample)
|
|
89
|
+
sample.close()
|
|
90
|
+
}
|
|
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 () => {
|
|
99
|
+
while (true) {
|
|
100
|
+
const {done, value} = await audioReader.read()
|
|
101
|
+
if (done) break
|
|
102
|
+
const sample = new AudioSample(value)
|
|
103
|
+
await audioSource.add(sample)
|
|
104
|
+
sample.close()
|
|
105
|
+
value.close()
|
|
106
|
+
}
|
|
107
|
+
})())
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await output.start()
|
|
111
|
+
await Promise.all(promises)
|
|
112
|
+
await output.finalize()
|
|
113
|
+
},
|
|
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
|
+
}))
|
|
136
|
+
)
|
|
137
|
+
|
|
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
|
+
type RenderableObject = Sprite | Text | Texture
|
|
165
|
+
|
|
166
|
+
async function renderLayer(
|
|
167
|
+
layer: Layer | Composition,
|
|
168
|
+
parent: Container,
|
|
169
|
+
) {
|
|
170
|
+
if (Array.isArray(layer)) {
|
|
171
|
+
const disposers: (() => void)[] = []
|
|
172
|
+
for (const child of layer) {
|
|
173
|
+
const result = await renderLayer(child, parent)
|
|
174
|
+
disposers.push(result.dispose)
|
|
175
|
+
}
|
|
176
|
+
return {dispose: () => disposers.forEach(d => d())}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
switch (layer.kind) {
|
|
180
|
+
case 'text':
|
|
181
|
+
return renderTextLayer(layer, parent)
|
|
182
|
+
case 'image':
|
|
183
|
+
return renderImageLayer(layer, parent)
|
|
184
|
+
case 'transition':
|
|
185
|
+
return renderTransitionLayer(layer, parent)
|
|
186
|
+
case 'gap': {
|
|
187
|
+
pixi?.renderer.clear()
|
|
188
|
+
return {dispose: () => {}}
|
|
189
|
+
}
|
|
190
|
+
default:
|
|
191
|
+
console.warn('Unknown layer kind', (layer as any).kind)
|
|
192
|
+
return {dispose: () => {}}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function renderTextLayer(
|
|
197
|
+
layer: Extract<Layer, {kind: 'text'}>,
|
|
198
|
+
parent: Container,
|
|
199
|
+
) {
|
|
200
|
+
const text = new Text({
|
|
201
|
+
text: layer.content,
|
|
202
|
+
style: {
|
|
203
|
+
fontFamily: 'sans-serif',
|
|
204
|
+
fontSize: layer.fontSize ?? 48,
|
|
205
|
+
fill: layer.color ?? 'white'
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
applyTransform(text, layer.matrix)
|
|
209
|
+
parent.addChild(text)
|
|
210
|
+
return {dispose: () => text.destroy(true)}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function renderImageLayer(
|
|
214
|
+
layer: Extract<Layer, {kind: 'image'}>,
|
|
215
|
+
parent: Container,
|
|
216
|
+
) {
|
|
217
|
+
const texture = Texture.from(layer.frame)
|
|
218
|
+
const sprite = new Sprite(texture)
|
|
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)
|
|
241
|
+
parent.addChild(sprite)
|
|
242
|
+
return {dispose: () => sprite.destroy(false)}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function applyTransform(target: Sprite | Text, worldMatrix?: Mat6) {
|
|
246
|
+
if (!worldMatrix) return
|
|
247
|
+
const mx = mat6ToMatrix(worldMatrix)
|
|
248
|
+
target.setFromMatrix(mx)
|
|
249
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// for later: https://github.com/gpac/mp4box.js/issues/243
|
|
2
|
+
export const encoderDefaultConfig: VideoEncoderConfig = {
|
|
3
|
+
codec: "avc1.640034",
|
|
4
|
+
avc: {format: "annexb"},
|
|
5
|
+
width: 1280,
|
|
6
|
+
height: 720,
|
|
7
|
+
bitrate: 9_000_000, // 9 Mbps
|
|
8
|
+
framerate: 60,
|
|
9
|
+
bitrateMode: "variable",
|
|
10
|
+
hardwareAcceleration: "no-preference" // prefer-hardware seems like 2x slower from what i been testing
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const audioEncoderDefaultConfig: AudioEncoderConfig = {
|
|
14
|
+
codec: "opus",
|
|
15
|
+
numberOfChannels: 2,
|
|
16
|
+
sampleRate: 44100
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {WebMediaInfo} from "web-demuxer"
|
|
2
|
+
|
|
3
|
+
type Events =
|
|
4
|
+
| {type: "config", config: {audio: AudioDecoderConfig, video: VideoDecoderConfig}}
|
|
5
|
+
| {type: "info", data: WebMediaInfo}
|
|
6
|
+
| {type: "encoderQueueSize", size: number}
|
|
7
|
+
|
|
8
|
+
type Handler = (event: Events) => void
|
|
9
|
+
|
|
10
|
+
export class Machina {
|
|
11
|
+
count = 0
|
|
12
|
+
|
|
13
|
+
#handlers = new Map<number, Handler>()
|
|
14
|
+
|
|
15
|
+
register(id: number, handler: Handler) {
|
|
16
|
+
this.#handlers.set(id, handler)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
unregister(id: number) {
|
|
20
|
+
this.#handlers.delete(id)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
dispatch(id: number, event: Events) {
|
|
24
|
+
this.#handlers.get(id)?.(event)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {BlobSource, UrlSource} from "mediabunny"
|
|
2
|
+
import {DecoderSource} from "../fns/schematic.js"
|
|
3
|
+
|
|
4
|
+
// only streamable sources
|
|
5
|
+
export async function loadDecoderSource(source: DecoderSource): Promise<UrlSource | BlobSource> {
|
|
6
|
+
if(source instanceof Blob) {
|
|
7
|
+
return new BlobSource(source)
|
|
8
|
+
} else {
|
|
9
|
+
return new UrlSource(source)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
import {pipeline} from "@huggingface/transformers"
|
|
3
|
+
|
|
4
|
+
import {TranscriberPipeOptions} from "../types.js"
|
|
5
|
+
|
|
6
|
+
export async function loadPipe(options: TranscriberPipeOptions) {
|
|
7
|
+
const {spec, onLoading} = options
|
|
8
|
+
|
|
9
|
+
const pipe = await pipeline("automatic-speech-recognition", spec.model, {
|
|
10
|
+
device: spec.device,
|
|
11
|
+
dtype: spec.dtype,
|
|
12
|
+
progress_callback: (data: any) => {
|
|
13
|
+
onLoading({total: data.total, progress: data.progress})
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return pipe
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
import {Driver} from "../../../../driver/driver.js"
|
|
3
|
+
|
|
4
|
+
export async function prepAudio(driver: Driver, source: Blob) {
|
|
5
|
+
const arrayBuffer = await source.arrayBuffer()
|
|
6
|
+
const audioCTX = new AudioContext({sampleRate: 16000})
|
|
7
|
+
const audioData = await audioCTX.decodeAudioData(arrayBuffer)
|
|
8
|
+
let audio: Float32Array
|
|
9
|
+
if (audioData.numberOfChannels === 2) {
|
|
10
|
+
const SCALING_FACTOR = Math.sqrt(2)
|
|
11
|
+
const left = audioData.getChannelData(0)
|
|
12
|
+
const right = audioData.getChannelData(1)
|
|
13
|
+
audio = new Float32Array(left.length)
|
|
14
|
+
for (let i = 0; i < audioData.length; ++i) {
|
|
15
|
+
audio[i] = (SCALING_FACTOR * (left[i] + right[i])) / 2
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
audio = audioData.getChannelData(0)
|
|
19
|
+
}
|
|
20
|
+
const duration = await driver.getAudioDuration(source)
|
|
21
|
+
return {audio, duration}
|
|
22
|
+
}
|
|
23
|
+
|