@omnimedia/omnitool 1.1.0-94 → 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/captions.ts +1 -0
- package/s/timeline/parts/item.ts +16 -5
- 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 +21 -1
- 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/captions.d.ts +1 -0
- package/x/timeline/parts/captions.js.map +1 -1
- package/x/timeline/parts/item.d.ts +15 -6
- 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 +14 -1
- 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">
|
|
@@ -5,6 +5,7 @@ import {TransformOptions, Vec2} from "../types.js"
|
|
|
5
5
|
import {Transcription, TranscriptSegment} from "../../features/speech/transcribe/types.js"
|
|
6
6
|
|
|
7
7
|
export type CaptionOptions = {
|
|
8
|
+
itemId?: Item.Caption["itemId"]
|
|
8
9
|
start?: number
|
|
9
10
|
duration?: number
|
|
10
11
|
styles?: TextStyleOptions
|
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 {
|
|
@@ -89,7 +90,16 @@ export namespace Item {
|
|
|
89
90
|
spatialId?: Id
|
|
90
91
|
animationIds?: Id[]
|
|
91
92
|
filterIds?: Id[]
|
|
92
|
-
|
|
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[]
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
export type Audio = {
|
|
@@ -99,7 +109,6 @@ export namespace Item {
|
|
|
99
109
|
start: number
|
|
100
110
|
duration: number
|
|
101
111
|
gain?: number
|
|
102
|
-
captionId?: Id
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
export type Text = {
|
|
@@ -118,6 +127,7 @@ export namespace Item {
|
|
|
118
127
|
id: Id
|
|
119
128
|
kind: Kind.Caption
|
|
120
129
|
transcript: Transcription
|
|
130
|
+
itemId?: Id
|
|
121
131
|
start: number
|
|
122
132
|
duration: number
|
|
123
133
|
maxChars?: number
|
|
@@ -140,6 +150,7 @@ export namespace Item {
|
|
|
140
150
|
| Sequence
|
|
141
151
|
| Stack
|
|
142
152
|
| Video
|
|
153
|
+
| Image
|
|
143
154
|
| Audio
|
|
144
155
|
| Text
|
|
145
156
|
| Caption
|
|
@@ -154,8 +165,8 @@ export namespace Item {
|
|
|
154
165
|
|
|
155
166
|
export type ContainerItem = Item.Sequence | Item.Stack
|
|
156
167
|
export type NonContainerItem = Exclude<Item.Any, ContainerItem>
|
|
157
|
-
export type FilterableItem = Item.Sequence | Item.Stack | Item.Video | Item.Text | Item.Caption
|
|
158
|
-
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
|
|
159
170
|
|
|
160
171
|
export type PlayableItem = Item.Any & {
|
|
161
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?: {
|
|
@@ -322,6 +341,7 @@ export class O {
|
|
|
322
341
|
id: this.getId(),
|
|
323
342
|
kind: Kind.Caption,
|
|
324
343
|
transcript,
|
|
344
|
+
itemId: options?.itemId,
|
|
325
345
|
start,
|
|
326
346
|
duration,
|
|
327
347
|
maxChars: options?.maxChars,
|
|
@@ -343,10 +363,10 @@ export class O {
|
|
|
343
363
|
const action = ((item: CaptionSourceItem, transcript: Transcription, options?: CaptionOptions): Item.Stack => {
|
|
344
364
|
const caption = make(transcript, {
|
|
345
365
|
...options,
|
|
366
|
+
itemId: item.id,
|
|
346
367
|
start: options?.start ?? item.start,
|
|
347
368
|
duration: options?.duration ?? item.duration,
|
|
348
369
|
})
|
|
349
|
-
this.set<CaptionSourceItem>(item.id, {captionId: caption.id})
|
|
350
370
|
return this.stack(caption, item)
|
|
351
371
|
}) as CaptionAction
|
|
352
372
|
|