@omnimedia/omnitool 1.1.0-95 → 1.1.0-96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/s/features/bg-remover/bg-remover.ts +26 -0
- package/s/features/bg-remover/default-spec.ts +11 -0
- package/s/features/bg-remover/types.ts +27 -0
- package/s/features/bg-remover/worker.bundle.ts +51 -0
- package/s/features/{speech/transcribe/parts → parts}/load-pipe.ts +3 -5
- package/s/features/parts/types.ts +19 -0
- package/s/features/speech/transcribe/default-spec.ts +2 -2
- package/s/features/speech/transcribe/types.ts +6 -16
- package/s/features/speech/transcribe/worker.bundle.ts +4 -3
- package/s/index.html.ts +2 -2
- package/s/timeline/parts/item.ts +15 -3
- package/s/timeline/parts/media.ts +11 -0
- package/s/timeline/renderers/parts/handy.ts +16 -0
- package/s/timeline/renderers/parts/samplers/visual/parts/defaults.ts +7 -1
- package/s/timeline/renderers/parts/samplers/visual/parts/image-sink.ts +51 -0
- package/s/timeline/renderers/parts/samplers/visual/parts/sample.ts +7 -0
- package/s/timeline/renderers/parts/samplers/visual/parts/types.ts +2 -1
- package/s/timeline/renderers/parts/samplers/visual/sampler.ts +8 -5
- package/s/timeline/sugar/helpers.ts +9 -0
- package/s/timeline/sugar/o.ts +19 -0
- package/x/demo/demo.bundle.min.js +100 -100
- package/x/demo/demo.bundle.min.js.map +4 -4
- package/x/features/bg-remover/bg-remover.d.ts +5 -0
- package/x/features/bg-remover/bg-remover.js +18 -0
- package/x/features/bg-remover/bg-remover.js.map +1 -0
- package/x/features/bg-remover/default-spec.d.ts +2 -0
- package/x/features/bg-remover/default-spec.js +6 -0
- package/x/features/bg-remover/default-spec.js.map +1 -0
- package/x/features/bg-remover/types.d.ts +20 -0
- package/x/features/bg-remover/types.js +2 -0
- package/x/features/bg-remover/types.js.map +1 -0
- package/x/features/bg-remover/worker.bundle.d.ts +1 -0
- package/x/features/bg-remover/worker.bundle.js +38 -0
- package/x/features/bg-remover/worker.bundle.js.map +1 -0
- package/x/features/bg-remover/worker.bundle.min.js +2916 -0
- package/x/features/bg-remover/worker.bundle.min.js.map +7 -0
- package/x/features/parts/load-pipe.d.ts +2 -0
- package/x/features/{speech/transcribe/parts → parts}/load-pipe.js +1 -1
- package/x/features/parts/load-pipe.js.map +1 -0
- package/x/features/parts/types.d.ts +15 -0
- package/x/features/parts/types.js +2 -0
- package/x/features/parts/types.js.map +1 -0
- package/x/features/speech/transcribe/default-spec.js +2 -2
- package/x/features/speech/transcribe/default-spec.js.map +1 -1
- package/x/features/speech/transcribe/types.d.ts +5 -14
- package/x/features/speech/transcribe/worker.bundle.js +3 -2
- package/x/features/speech/transcribe/worker.bundle.js.map +1 -1
- package/x/features/speech/transcribe/worker.bundle.min.js +1 -1
- package/x/features/speech/transcribe/worker.bundle.min.js.map +3 -3
- package/x/index.html +4 -4
- package/x/index.html.js +2 -2
- package/x/tests.bundle.min.js +103 -103
- package/x/tests.bundle.min.js.map +4 -4
- package/x/tests.html +1 -1
- package/x/timeline/parts/item.d.ts +14 -4
- package/x/timeline/parts/item.js +1 -0
- package/x/timeline/parts/item.js.map +1 -1
- package/x/timeline/parts/media.d.ts +1 -0
- package/x/timeline/parts/media.js +8 -0
- package/x/timeline/parts/media.js.map +1 -1
- package/x/timeline/renderers/parts/handy.d.ts +1 -0
- package/x/timeline/renderers/parts/handy.js +11 -0
- package/x/timeline/renderers/parts/handy.js.map +1 -1
- package/x/timeline/renderers/parts/samplers/visual/parts/defaults.d.ts +4 -1
- package/x/timeline/renderers/parts/samplers/visual/parts/defaults.js +3 -0
- package/x/timeline/renderers/parts/samplers/visual/parts/defaults.js.map +1 -1
- package/x/timeline/renderers/parts/samplers/visual/parts/image-sink.d.ts +11 -0
- package/x/timeline/renderers/parts/samplers/visual/parts/image-sink.js +36 -0
- package/x/timeline/renderers/parts/samplers/visual/parts/image-sink.js.map +1 -0
- package/x/timeline/renderers/parts/samplers/visual/parts/sample.js +6 -0
- package/x/timeline/renderers/parts/samplers/visual/parts/sample.js.map +1 -1
- package/x/timeline/renderers/parts/samplers/visual/parts/types.d.ts +2 -1
- package/x/timeline/renderers/parts/samplers/visual/parts/{sink.js → video-sink.js} +1 -1
- package/x/timeline/renderers/parts/samplers/visual/parts/video-sink.js.map +1 -0
- package/x/timeline/renderers/parts/samplers/visual/sampler.js +8 -5
- package/x/timeline/renderers/parts/samplers/visual/sampler.js.map +1 -1
- package/x/timeline/sugar/helpers.d.ts +3 -0
- package/x/timeline/sugar/helpers.js +3 -0
- package/x/timeline/sugar/helpers.js.map +1 -1
- package/x/timeline/sugar/o.d.ts +3 -0
- package/x/timeline/sugar/o.js +12 -0
- package/x/timeline/sugar/o.js.map +1 -1
- package/x/features/speech/transcribe/parts/load-pipe.d.ts +0 -2
- package/x/features/speech/transcribe/parts/load-pipe.js.map +0 -1
- package/x/timeline/renderers/parts/samplers/visual/parts/sink.js.map +0 -1
- /package/s/timeline/renderers/parts/samplers/visual/parts/{sink.ts → video-sink.ts} +0 -0
- /package/x/timeline/renderers/parts/samplers/visual/parts/{sink.d.ts → video-sink.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {queue} from "@e280/stz"
|
|
2
|
+
import {Comrade, LoggerTap, tune} from "@e280/comrade"
|
|
3
|
+
|
|
4
|
+
import {BgRemoverOptions, BgRemoverSchematic, RemoverOptions} from "./types.js"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export async function makeBgRemover({spec, workerUrl, onLoading}: BgRemoverOptions) {
|
|
8
|
+
const thread = await Comrade.thread<BgRemoverSchematic>({
|
|
9
|
+
label: "OmnitoolBgRemover",
|
|
10
|
+
workerUrl,
|
|
11
|
+
tap: new LoggerTap(),
|
|
12
|
+
setupHost: () => ({
|
|
13
|
+
loading: async loading => onLoading(loading),
|
|
14
|
+
}),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
await thread.work.prepare(spec)
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
remove: queue(async(input: RemoverOptions) =>
|
|
21
|
+
await thread.work.remove[tune]({transfer: [input.frame]})(input.frame)
|
|
22
|
+
),
|
|
23
|
+
dispose: () => thread.terminate()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import {AsSchematic} from "@e280/comrade"
|
|
3
|
+
import {Loading, PipelineSpec} from "../parts/types.js"
|
|
4
|
+
|
|
5
|
+
export type BgRemoverSchematic = AsSchematic<{
|
|
6
|
+
work: {
|
|
7
|
+
prepare(spec: PipelineSpec): Promise<void>
|
|
8
|
+
remove(request: VideoFrame): Promise<VideoFrame>
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
host: {
|
|
12
|
+
loading(load: Loading): Promise<void>
|
|
13
|
+
}
|
|
14
|
+
}>
|
|
15
|
+
|
|
16
|
+
export type RemoverOptions = {
|
|
17
|
+
frame: VideoFrame
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type BgRemoverModels = "onnx-community/ISNet-ONNX" | "Xenova/modnet" | "briaai/RMBG-1.4"
|
|
21
|
+
|
|
22
|
+
export type BgRemoverOptions = {
|
|
23
|
+
spec: PipelineSpec
|
|
24
|
+
workerUrl: URL | string
|
|
25
|
+
onLoading: (loading: Loading) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
import {defer, once} from "@e280/stz"
|
|
3
|
+
import {Comrade, Host} from "@e280/comrade"
|
|
4
|
+
import {BackgroundRemovalPipeline} from "@huggingface/transformers"
|
|
5
|
+
|
|
6
|
+
import {PipelineSpec} from "../parts/types.js"
|
|
7
|
+
import {BgRemoverSchematic} from "./types.js"
|
|
8
|
+
import {loadPipe} from "../parts/load-pipe.js"
|
|
9
|
+
|
|
10
|
+
const deferred = defer<{spec: PipelineSpec, pipe: BackgroundRemovalPipeline}>()
|
|
11
|
+
const makePrepare = (host: Host<BgRemoverSchematic>) => once(async(spec: PipelineSpec) => {
|
|
12
|
+
deferred.resolve({
|
|
13
|
+
spec,
|
|
14
|
+
pipe: await loadPipe({
|
|
15
|
+
spec,
|
|
16
|
+
task: "background-removal",
|
|
17
|
+
onLoading: loading => host.loading(loading),
|
|
18
|
+
}) as BackgroundRemovalPipeline
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const canvas = new OffscreenCanvas(1920, 1080)
|
|
23
|
+
const ctx = canvas.getContext("2d")
|
|
24
|
+
|
|
25
|
+
await Comrade.worker<BgRemoverSchematic>(shell => {
|
|
26
|
+
const prepare = makePrepare(shell.host)
|
|
27
|
+
return {
|
|
28
|
+
prepare,
|
|
29
|
+
async remove(request) {
|
|
30
|
+
const {pipe} = await deferred.promise
|
|
31
|
+
|
|
32
|
+
canvas.width = request.displayWidth
|
|
33
|
+
canvas.height = request.displayHeight
|
|
34
|
+
ctx?.drawImage(request, 0, 0)
|
|
35
|
+
|
|
36
|
+
const output = await pipe(canvas)
|
|
37
|
+
const mask = output[0]
|
|
38
|
+
|
|
39
|
+
const frame = new VideoFrame(mask.toCanvas(), {
|
|
40
|
+
timestamp: request.timestamp,
|
|
41
|
+
duration: request.duration ?? undefined,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
request.close()
|
|
45
|
+
shell.transfer = [frame]
|
|
46
|
+
return frame
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
import {pipeline} from "@huggingface/transformers"
|
|
3
|
+
import {PipeOptions} from "./types.js"
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export async function loadPipe(options: TranscriberPipeOptions) {
|
|
5
|
+
export async function loadPipe(options: PipeOptions) {
|
|
7
6
|
const {spec, onLoading} = options
|
|
8
7
|
|
|
9
|
-
const pipe = await pipeline(
|
|
8
|
+
const pipe = await pipeline(options.task, spec.model, {
|
|
10
9
|
device: spec.device,
|
|
11
10
|
dtype: spec.dtype,
|
|
12
11
|
progress_callback: (data: any) => {
|
|
@@ -16,4 +15,3 @@ export async function loadPipe(options: TranscriberPipeOptions) {
|
|
|
16
15
|
|
|
17
16
|
return pipe
|
|
18
17
|
}
|
|
19
|
-
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
import {DataType, DeviceType, TaskType} from "@huggingface/transformers"
|
|
3
|
+
|
|
4
|
+
export type Loading = {
|
|
5
|
+
total: number
|
|
6
|
+
progress: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type PipelineSpec<Extras extends object = {}> = {
|
|
10
|
+
model: string
|
|
11
|
+
dtype: DataType
|
|
12
|
+
device: DeviceType
|
|
13
|
+
} & Extras
|
|
14
|
+
|
|
15
|
+
export type PipeOptions = {
|
|
16
|
+
spec: PipelineSpec
|
|
17
|
+
task: TaskType
|
|
18
|
+
onLoading: (loading: Loading) => void
|
|
19
|
+
}
|
|
@@ -3,8 +3,8 @@ import {TranscriberSpec} from "./types.js"
|
|
|
3
3
|
|
|
4
4
|
export const defaultTranscriberSpec = (): TranscriberSpec => ({
|
|
5
5
|
model: "onnx-community/whisper-tiny_timestamped",
|
|
6
|
-
dtype: "
|
|
7
|
-
device: "
|
|
6
|
+
dtype: "auto",
|
|
7
|
+
device: "webgpu",
|
|
8
8
|
chunkLength: 20,
|
|
9
9
|
strideLength: 3,
|
|
10
10
|
})
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import {AsSchematic} from "@e280/comrade"
|
|
3
|
-
import {
|
|
3
|
+
import {Pipeline} from "@huggingface/transformers"
|
|
4
4
|
|
|
5
|
+
import {Loading, PipelineSpec} from "../../parts/types.js"
|
|
5
6
|
import {Driver} from "../../../driver/driver.js"
|
|
6
7
|
|
|
7
8
|
export type TranscriberSchematic = AsSchematic<{
|
|
@@ -17,11 +18,6 @@ export type TranscriberSchematic = AsSchematic<{
|
|
|
17
18
|
}
|
|
18
19
|
}>
|
|
19
20
|
|
|
20
|
-
export type Loading = {
|
|
21
|
-
total: number
|
|
22
|
-
progress: number
|
|
23
|
-
}
|
|
24
|
-
|
|
25
21
|
export type TranscribeOptions = {
|
|
26
22
|
pipe: Pipeline
|
|
27
23
|
spec: TranscriberSpec
|
|
@@ -29,11 +25,6 @@ export type TranscribeOptions = {
|
|
|
29
25
|
callbacks: TranscriptionCallbacks
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
export type TranscriberPipeOptions = {
|
|
33
|
-
spec: TranscriberSpec
|
|
34
|
-
onLoading: (loading: Loading) => void
|
|
35
|
-
}
|
|
36
|
-
|
|
37
28
|
export type SpeechTime = [start: number, end: number]
|
|
38
29
|
|
|
39
30
|
export type TranscriptWord = {
|
|
@@ -48,13 +39,12 @@ export type Transcription = {
|
|
|
48
39
|
chunks: TranscriptWord[]
|
|
49
40
|
}
|
|
50
41
|
|
|
51
|
-
export type
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
device: DeviceType
|
|
42
|
+
export type TranscriberModels = "onnx-community/whisper-tiny_timestamped"
|
|
43
|
+
|
|
44
|
+
export type TranscriberSpec = PipelineSpec<{
|
|
55
45
|
chunkLength: number
|
|
56
46
|
strideLength: number
|
|
57
|
-
}
|
|
47
|
+
}>
|
|
58
48
|
|
|
59
49
|
export type TranscriptionOptions = {
|
|
60
50
|
source: Blob
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
import {defer, once} from "@e280/stz"
|
|
3
3
|
import {Comrade, Host} from "@e280/comrade"
|
|
4
|
-
import {Pipeline} from "@huggingface/transformers"
|
|
4
|
+
import {AutomaticSpeechRecognitionPipeline, Pipeline} from "@huggingface/transformers"
|
|
5
5
|
|
|
6
|
-
import {loadPipe} from "
|
|
6
|
+
import {loadPipe} from "../../parts/load-pipe.js"
|
|
7
7
|
import {transcribe} from "./parts/transcribe.js"
|
|
8
8
|
import {TranscriberSchematic, TranscriberSpec} from "./types.js"
|
|
9
9
|
|
|
@@ -14,8 +14,9 @@ const makePrepare = (host: Host<TranscriberSchematic>) => once(async(spec: Trans
|
|
|
14
14
|
spec,
|
|
15
15
|
pipe: await loadPipe({
|
|
16
16
|
spec,
|
|
17
|
+
task: "automatic-speech-recognition",
|
|
17
18
|
onLoading: loading => host.loading(loading),
|
|
18
|
-
})
|
|
19
|
+
}) as AutomaticSpeechRecognitionPipeline
|
|
19
20
|
})
|
|
20
21
|
})
|
|
21
22
|
|
package/s/index.html.ts
CHANGED
|
@@ -96,7 +96,7 @@ export default ssg.page(import.meta.url, async orb => ({
|
|
|
96
96
|
<p>Build timeline and run the playback engine.</p>
|
|
97
97
|
</header>
|
|
98
98
|
<div class="demo-controls">
|
|
99
|
-
<input type="file" accept="video/*,audio/*" />
|
|
99
|
+
<input type="file" accept="video/*,audio/*,image/*" />
|
|
100
100
|
</div>
|
|
101
101
|
<div class="player-canvas"></div>
|
|
102
102
|
<div class="player">
|
|
@@ -120,7 +120,7 @@ export default ssg.page(import.meta.url, async orb => ({
|
|
|
120
120
|
<p>Build timeline and export a render.</p>
|
|
121
121
|
</header>
|
|
122
122
|
<div class="demo-controls">
|
|
123
|
-
<input type="file" accept="video/*,audio/*" />
|
|
123
|
+
<input type="file" accept="video/*,audio/*,image/*" />
|
|
124
124
|
<button data-action="export" disabled>Export</button>
|
|
125
125
|
</div>
|
|
126
126
|
<div class="demo-progress">
|
package/s/timeline/parts/item.ts
CHANGED
|
@@ -21,7 +21,8 @@ export enum Kind {
|
|
|
21
21
|
Transition,
|
|
22
22
|
TextStyle,
|
|
23
23
|
Filter,
|
|
24
|
-
Caption
|
|
24
|
+
Caption,
|
|
25
|
+
Image
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export enum Effect {
|
|
@@ -91,6 +92,16 @@ export namespace Item {
|
|
|
91
92
|
filterIds?: Id[]
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
export type Image = {
|
|
96
|
+
id: Id
|
|
97
|
+
kind: Kind.Image
|
|
98
|
+
mediaHash: Hash
|
|
99
|
+
duration: number
|
|
100
|
+
spatialId?: Id
|
|
101
|
+
animationIds?: Id[]
|
|
102
|
+
filterIds?: Id[]
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
export type Audio = {
|
|
95
106
|
id: Id
|
|
96
107
|
kind: Kind.Audio
|
|
@@ -139,6 +150,7 @@ export namespace Item {
|
|
|
139
150
|
| Sequence
|
|
140
151
|
| Stack
|
|
141
152
|
| Video
|
|
153
|
+
| Image
|
|
142
154
|
| Audio
|
|
143
155
|
| Text
|
|
144
156
|
| Caption
|
|
@@ -153,8 +165,8 @@ export namespace Item {
|
|
|
153
165
|
|
|
154
166
|
export type ContainerItem = Item.Sequence | Item.Stack
|
|
155
167
|
export type NonContainerItem = Exclude<Item.Any, ContainerItem>
|
|
156
|
-
export type FilterableItem = Item.Sequence | Item.Stack | Item.Video | Item.Text | Item.Caption
|
|
157
|
-
export type VisualAnimatableItem = Item.Video | Item.Text | Item.Caption
|
|
168
|
+
export type FilterableItem = Item.Sequence | Item.Stack | Item.Video | Item.Image | Item.Text | Item.Caption
|
|
169
|
+
export type VisualAnimatableItem = Item.Video | Item.Image | Item.Text | Item.Caption
|
|
158
170
|
|
|
159
171
|
export type PlayableItem = Item.Any & {
|
|
160
172
|
start: Ms
|
|
@@ -7,6 +7,7 @@ import {loadDecoderSource} from "../../driver/utils/load-decoder-source.js"
|
|
|
7
7
|
|
|
8
8
|
export class Media {
|
|
9
9
|
duration = 0
|
|
10
|
+
isImage = false
|
|
10
11
|
hasVideo = false
|
|
11
12
|
hasAudio = false
|
|
12
13
|
|
|
@@ -14,6 +15,12 @@ export class Media {
|
|
|
14
15
|
|
|
15
16
|
static async analyze(datafile: Datafile) {
|
|
16
17
|
const media = new this(datafile)
|
|
18
|
+
|
|
19
|
+
if (this.#isImage(datafile)) {
|
|
20
|
+
media.isImage = true
|
|
21
|
+
return media
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
const duration = (await this.duration(datafile.url)) * 1000
|
|
18
25
|
media.duration = duration
|
|
19
26
|
const {video, audio} = await this.#has(datafile.url)
|
|
@@ -22,6 +29,10 @@ export class Media {
|
|
|
22
29
|
return media
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
static #isImage(datafile: Datafile) {
|
|
33
|
+
return datafile.blob.type.startsWith("image/")
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
static async duration(source: DecoderSource) {
|
|
26
37
|
const input = new Input({
|
|
27
38
|
formats: ALL_FORMATS,
|
|
@@ -15,6 +15,7 @@ type WalkAtCallbacks = {
|
|
|
15
15
|
sequence: (x: Item.Sequence, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
16
16
|
stack: (x: Item.Stack, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
17
17
|
video: (x: Item.Video, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
18
|
+
image: (x: Item.Image, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
18
19
|
text: (x: Item.Text, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
19
20
|
caption: (x: Item.Caption, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
20
21
|
audio: (x: Item.Audio, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
@@ -24,6 +25,7 @@ type WalkCallbacks = {
|
|
|
24
25
|
sequence?: (x: Item.Sequence, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
25
26
|
stack?: (x: Item.Stack, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
26
27
|
video?: (x: Item.Video, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
28
|
+
image?: (x: Item.Image, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
27
29
|
text?: (x: Item.Text, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
28
30
|
caption?: (x: Item.Caption, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
29
31
|
audio?: (x: Item.Audio) => void
|
|
@@ -53,6 +55,7 @@ export function itemsAt(p: Props): At[] {
|
|
|
53
55
|
sequence: () => { },
|
|
54
56
|
stack: () => { },
|
|
55
57
|
video: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
|
|
58
|
+
image: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
|
|
56
59
|
text: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
|
|
57
60
|
caption: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
|
|
58
61
|
audio: (item, localTime, ancestors) => results.push({ item, localTime, ancestors })
|
|
@@ -74,6 +77,7 @@ export function itemsFrom(p: FromProps): At[] {
|
|
|
74
77
|
sequence: () => { },
|
|
75
78
|
stack: () => { },
|
|
76
79
|
video: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
|
|
80
|
+
image: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
|
|
77
81
|
text: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
|
|
78
82
|
caption: (item, localTime, ancestors) => results.push({ item, localTime, ancestors }),
|
|
79
83
|
audio: (item, localTime, ancestors) => results.push({ item, localTime, ancestors })
|
|
@@ -176,6 +180,10 @@ export function walk(
|
|
|
176
180
|
callbacks.video?.(item, currentMatrix, ancestors)
|
|
177
181
|
break
|
|
178
182
|
|
|
183
|
+
case Kind.Image:
|
|
184
|
+
callbacks.image?.(item, currentMatrix, ancestors)
|
|
185
|
+
break
|
|
186
|
+
|
|
179
187
|
case Kind.Text:
|
|
180
188
|
callbacks.text?.(item, currentMatrix, ancestors)
|
|
181
189
|
break
|
|
@@ -246,6 +254,10 @@ function walkAt(
|
|
|
246
254
|
callbacks.video(item, time, ancestors)
|
|
247
255
|
break
|
|
248
256
|
|
|
257
|
+
case Kind.Image:
|
|
258
|
+
callbacks.image(item, time, ancestors)
|
|
259
|
+
break
|
|
260
|
+
|
|
249
261
|
case Kind.Text:
|
|
250
262
|
callbacks.text(item, time, ancestors)
|
|
251
263
|
break
|
|
@@ -318,6 +330,10 @@ function walkFrom(
|
|
|
318
330
|
callbacks.video(item, from, ancestors)
|
|
319
331
|
break
|
|
320
332
|
|
|
333
|
+
case Kind.Image:
|
|
334
|
+
callbacks.image(item, from, ancestors)
|
|
335
|
+
break
|
|
336
|
+
|
|
321
337
|
case Kind.Text:
|
|
322
338
|
callbacks.text(item, from, ancestors)
|
|
323
339
|
break
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
|
|
2
|
-
import {VideoSink} from "./sink.js"
|
|
2
|
+
import {VideoSink} from "./video-sink.js"
|
|
3
|
+
import {ImageSink} from "./image-sink.js"
|
|
3
4
|
import {Ms} from "../../../../../../units/ms.js"
|
|
4
5
|
import {Item} from "../../../../../parts/item.js"
|
|
5
6
|
|
|
6
7
|
export type VideoSampler = (item: Item.Video, time: Ms) => Promise<VideoFrame | undefined>
|
|
8
|
+
export type ImageSampler = (item: Item.Image, time: Ms) => Promise<VideoFrame | undefined>
|
|
7
9
|
|
|
8
10
|
export function createDefaultVideoSampler(sink: VideoSink): VideoSampler {
|
|
9
11
|
return async (item, time) => {
|
|
@@ -15,3 +17,7 @@ export function createDefaultVideoSampler(sink: VideoSink): VideoSampler {
|
|
|
15
17
|
return frame ?? undefined
|
|
16
18
|
}
|
|
17
19
|
}
|
|
20
|
+
|
|
21
|
+
export function createDefaultImageSampler(sink: ImageSink): ImageSampler {
|
|
22
|
+
return async (_item, time) => await sink.getFrame(_item.mediaHash, time)
|
|
23
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
import {Ms} from "../../../../../../units/ms.js"
|
|
3
|
+
import {Hash} from "../../../../../parts/basics.js"
|
|
4
|
+
import {DecoderSource} from "../../../../../../driver/fns/schematic.js"
|
|
5
|
+
|
|
6
|
+
type CachedImage = {
|
|
7
|
+
bitmap: ImageBitmap
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ImageSink {
|
|
11
|
+
readonly #images = new Map<Hash, CachedImage>()
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
private resolveMedia: (hash: string) => DecoderSource,
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
async getFrame(hash: Hash, time: Ms) {
|
|
18
|
+
const image = await this.#getImage(hash)
|
|
19
|
+
return new VideoFrame(image.bitmap, {
|
|
20
|
+
timestamp: Math.round(time * 1000),
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async #getImage(hash: Hash) {
|
|
25
|
+
const existing = this.#images.get(hash)
|
|
26
|
+
if (existing)
|
|
27
|
+
return existing
|
|
28
|
+
|
|
29
|
+
const source = this.resolveMedia(hash)
|
|
30
|
+
const blob = source instanceof Blob
|
|
31
|
+
? source
|
|
32
|
+
: await fetch(source).then(response => response.blob())
|
|
33
|
+
const image = {bitmap: await createImageBitmap(blob)}
|
|
34
|
+
|
|
35
|
+
this.#images.set(hash, image)
|
|
36
|
+
return image
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
disposeAll() {
|
|
40
|
+
for (const image of this.#images.values())
|
|
41
|
+
image.bitmap.close()
|
|
42
|
+
this.#images.clear()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
dispose(hash: Hash) {
|
|
46
|
+
const image = this.#images.get(hash)
|
|
47
|
+
image?.bitmap.close()
|
|
48
|
+
this.#images.delete(hash)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
@@ -49,6 +49,13 @@ export async function sampleVisual(
|
|
|
49
49
|
return frame ? [{kind: "image", frame, matrix, alpha, crop, filters, id: item.id}] : []
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
case Kind.Image: {
|
|
53
|
+
if (time < 0 || time >= item.duration) return []
|
|
54
|
+
|
|
55
|
+
const frame = await ctx.imageSampler(item, time)
|
|
56
|
+
return frame ? [{kind: "image", frame, matrix, alpha, crop, filters, id: item.id}] : []
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
case Kind.Text: {
|
|
53
60
|
if (time < 0 || time >= item.duration) return []
|
|
54
61
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
|
|
2
|
-
import {VideoSampler} from "./defaults.js"
|
|
2
|
+
import {ImageSampler, VideoSampler} from "./defaults.js"
|
|
3
3
|
import {Item} from "../../../../../parts/item.js"
|
|
4
4
|
import {TimelineFile} from "../../../../../parts/basics.js"
|
|
5
5
|
|
|
6
6
|
export type SampleContext = {
|
|
7
|
+
imageSampler: ImageSampler
|
|
7
8
|
videoSampler: VideoSampler
|
|
8
9
|
timeline: TimelineFile
|
|
9
10
|
items: Map<number, Item.Any>
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
|
|
2
|
-
import {VideoSink} from "./parts/sink.js"
|
|
2
|
+
import {VideoSink} from "./parts/video-sink.js"
|
|
3
|
+
import {ImageSink} from "./parts/image-sink.js"
|
|
3
4
|
import {sampleVisual} from "./parts/sample.js"
|
|
4
5
|
import {Ms} from "../../../../../units/ms.js"
|
|
5
6
|
import {TimelineFile} from "../../../../parts/basics.js"
|
|
6
7
|
import {DecoderSource} from "../../../../../driver/fns/schematic.js"
|
|
7
|
-
import {createDefaultVideoSampler, VideoSampler} from "./parts/defaults.js"
|
|
8
|
+
import {createDefaultImageSampler, createDefaultVideoSampler, VideoSampler} from "./parts/defaults.js"
|
|
8
9
|
|
|
9
10
|
export function createVisualSampler(
|
|
10
11
|
resolveMedia: (hash: string) => DecoderSource,
|
|
11
12
|
sampleVideo?: VideoSampler
|
|
12
13
|
) {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
14
|
+
const imageSink = new ImageSink(resolveMedia)
|
|
15
|
+
const videoSink = new VideoSink(resolveMedia)
|
|
16
|
+
const imageSampler = createDefaultImageSampler(imageSink)
|
|
17
|
+
const videoSampler = sampleVideo ?? createDefaultVideoSampler(videoSink)
|
|
15
18
|
|
|
16
19
|
return {
|
|
17
20
|
async sample(timeline: TimelineFile, timecode: Ms) {
|
|
@@ -21,7 +24,7 @@ export function createVisualSampler(
|
|
|
21
24
|
if (!root)
|
|
22
25
|
return []
|
|
23
26
|
|
|
24
|
-
return sampleVisual({videoSampler, timeline, items}, root, timecode, [])
|
|
27
|
+
return sampleVisual({imageSampler, videoSampler, timeline, items}, root, timecode, [])
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
}
|
|
@@ -56,6 +56,15 @@ export function video(
|
|
|
56
56
|
return o => o.video(media, options)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
export function image(
|
|
60
|
+
media: Media,
|
|
61
|
+
options?: {
|
|
62
|
+
duration?: number
|
|
63
|
+
}
|
|
64
|
+
): Build<Item.Image> {
|
|
65
|
+
return o => o.image(media, options)
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
export function audio(
|
|
60
69
|
media: Media,
|
|
61
70
|
options?: {
|
package/s/timeline/sugar/o.ts
CHANGED
|
@@ -269,6 +269,25 @@ export class O {
|
|
|
269
269
|
return item
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
image = (
|
|
273
|
+
media: Media,
|
|
274
|
+
options?: {
|
|
275
|
+
duration?: number
|
|
276
|
+
}): Item.Image => {
|
|
277
|
+
|
|
278
|
+
if(!media.isImage)
|
|
279
|
+
throw new Error(`Image error: media "${media.datafile.filename}" is not an image.`)
|
|
280
|
+
|
|
281
|
+
const item: Item.Image = {
|
|
282
|
+
kind: Kind.Image,
|
|
283
|
+
id: this.getId(),
|
|
284
|
+
mediaHash: media.datafile.checksum.hash,
|
|
285
|
+
duration: options?.duration ?? 2000
|
|
286
|
+
}
|
|
287
|
+
this.register(item)
|
|
288
|
+
return item
|
|
289
|
+
}
|
|
290
|
+
|
|
272
291
|
audio = (
|
|
273
292
|
media: Media,
|
|
274
293
|
options?: {
|