@omnimedia/omnitool 1.0.0 → 1.1.0-3
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 +56 -27
- package/s/_archive/types.ts +107 -0
- package/s/context.ts +7 -0
- package/s/demo/demo.bundle.ts +64 -0
- package/s/demo/demo.css +54 -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 +44 -0
- package/s/demo/routines/waveform-test.ts +12 -0
- package/s/driver/driver.test.ts +15 -0
- package/s/driver/driver.ts +116 -0
- package/s/driver/driver.worker.bundle.ts +7 -0
- package/s/driver/fns/host.ts +12 -0
- package/s/driver/fns/schematic.ts +83 -0
- package/s/driver/fns/work.ts +237 -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 +13 -0
- package/s/driver/utils/sleep.ts +3 -0
- package/s/index.html.ts +53 -0
- package/s/index.ts +2 -39
- package/s/tests.test.ts +8 -0
- package/s/timeline/index.ts +14 -0
- package/s/timeline/parts/basics.ts +17 -0
- package/s/timeline/parts/filmstrip.ts +159 -0
- package/s/timeline/parts/item.ts +58 -0
- package/s/timeline/parts/media.ts +14 -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/o.ts +60 -0
- package/s/timeline/sugar/omni-test.ts +38 -0
- package/s/timeline/sugar/omni.ts +30 -0
- package/s/timeline/utils/checksum.ts +19 -0
- package/s/timeline/utils/datafile.ts +21 -0
- package/s/timeline/utils/dummy-data.ts +7 -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 +51 -0
- package/x/demo/demo.bundle.js.map +1 -0
- package/x/demo/demo.bundle.min.js +118 -0
- package/x/demo/demo.bundle.min.js.map +7 -0
- package/x/demo/demo.css +54 -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 +38 -0
- package/x/demo/routines/transcode-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 +22 -0
- package/x/driver/driver.js +97 -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 +1148 -0
- package/x/driver/driver.worker.bundle.min.js.map +7 -0
- package/x/driver/fns/host.d.ts +18 -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 +66 -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 +19 -0
- package/x/driver/fns/work.js +192 -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 +2 -0
- package/x/driver/utils/load-decoder-source.js +12 -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/index.d.ts +2 -9
- package/x/index.html +105 -0
- package/x/index.html.d.ts +2 -0
- package/x/index.html.js +47 -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 +10 -0
- package/x/timeline/index.js +11 -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/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 +42 -0
- package/x/timeline/parts/item.js +13 -0
- package/x/timeline/parts/item.js.map +1 -0
- package/x/timeline/parts/media.d.ts +7 -0
- package/x/timeline/parts/media.js +13 -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/o.d.ts +14 -0
- package/x/timeline/sugar/o.js +48 -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 +11 -0
- package/x/timeline/sugar/omni.js +20 -0
- package/x/timeline/sugar/omni.js.map +1 -0
- package/x/timeline/utils/checksum.d.ts +8 -0
- package/x/timeline/utils/checksum.js +20 -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/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 +0 -2
- package/x/types.js.map +0 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ALL_FORMATS,
|
|
3
|
+
CanvasSink,
|
|
4
|
+
CanvasSinkOptions,
|
|
5
|
+
Input,
|
|
6
|
+
InputVideoTrack,
|
|
7
|
+
WrappedCanvas,
|
|
8
|
+
} from "mediabunny"
|
|
9
|
+
|
|
10
|
+
import {DecoderSource} from '../../driver/fns/schematic.js'
|
|
11
|
+
import {loadDecoderSource} from '../../driver/utils/load-decoder-source.js'
|
|
12
|
+
|
|
13
|
+
export class Filmstrip {
|
|
14
|
+
#sink: CanvasSink
|
|
15
|
+
#cache: Map<number, WrappedCanvas> = new Map()
|
|
16
|
+
#activeRange: TimeRange = [0, 0]
|
|
17
|
+
|
|
18
|
+
private constructor(
|
|
19
|
+
private videoTrack: InputVideoTrack,
|
|
20
|
+
private options: Required<FilmstripOptions>
|
|
21
|
+
) {
|
|
22
|
+
this.#sink = new CanvasSink(videoTrack, options.canvasSinkOptions)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async init(source: DecoderSource, options: FilmstripOptions) {
|
|
26
|
+
const input = new Input({
|
|
27
|
+
formats: ALL_FORMATS,
|
|
28
|
+
source: await loadDecoderSource(source)
|
|
29
|
+
})
|
|
30
|
+
const videoTrack = await input.getPrimaryVideoTrack()
|
|
31
|
+
if(videoTrack)
|
|
32
|
+
return new Filmstrip(
|
|
33
|
+
videoTrack, {
|
|
34
|
+
frequency: options.frequency ?? 1,
|
|
35
|
+
canvasSinkOptions: options.canvasSinkOptions ?? {width: 80, height: 50, fit: "fill"},
|
|
36
|
+
onChange: options.onChange
|
|
37
|
+
})
|
|
38
|
+
else throw new Error("Source has no video track")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Sets the frequency (granularity) of filmstrip thumbnails.
|
|
43
|
+
* Changing this triggers a filmstrip refresh after any ongoing update finishes.
|
|
44
|
+
* @param value - The new frequency in seconds.
|
|
45
|
+
*/
|
|
46
|
+
set frequency(value: number) {
|
|
47
|
+
if(value !== this.options.frequency) {
|
|
48
|
+
this.options.frequency = value
|
|
49
|
+
this.#update()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get frequency() {
|
|
54
|
+
return this.options.frequency
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#computeActiveRange([start, end]: TimeRange): TimeRange {
|
|
58
|
+
const tileSize = end - start
|
|
59
|
+
return [start - tileSize, end + tileSize]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async #generateTiles() {
|
|
63
|
+
const [rangeStart, rangeEnd] = this.#activeRange
|
|
64
|
+
const neededTimestamps = new Set<number>()
|
|
65
|
+
|
|
66
|
+
// duration should be computed but with trim etc also
|
|
67
|
+
const duration = await this.videoTrack.computeDuration()
|
|
68
|
+
for (
|
|
69
|
+
let timestamp = rangeStart;
|
|
70
|
+
timestamp <= rangeEnd;
|
|
71
|
+
timestamp += this.options.frequency
|
|
72
|
+
) {
|
|
73
|
+
// Clamp to valid time range
|
|
74
|
+
if (timestamp >= 0 && timestamp <= duration)
|
|
75
|
+
neededTimestamps.add(+timestamp.toFixed(3))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const missingTimestamps = [...neededTimestamps]
|
|
79
|
+
.filter(t => !this.#cache.has(t))
|
|
80
|
+
|
|
81
|
+
let i = 0
|
|
82
|
+
for await (const canvas of this.#sink.canvasesAtTimestamps(missingTimestamps)) {
|
|
83
|
+
if(canvas) {
|
|
84
|
+
const requestedTime = missingTimestamps[i++]
|
|
85
|
+
this.#cache.set(requestedTime, canvas)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Dispose canvases outside the new range
|
|
90
|
+
for (const key of this.#cache.keys()) {
|
|
91
|
+
if (!neededTimestamps.has(key)) {
|
|
92
|
+
this.#cache.delete(key)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const tiles = [...this.#cache.entries()]
|
|
97
|
+
.map(([time, canvas]) => ({time, canvas}))
|
|
98
|
+
this.options.onChange(tiles)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Updates the visible time range for the filmstrip.
|
|
103
|
+
*
|
|
104
|
+
* Triggers a thumbnails update, with extended margins to preload
|
|
105
|
+
* thumbnails slightly outside the visible range.
|
|
106
|
+
* @param visibleRange - The current timeline viewport as a [start, end] tuple in seconds.
|
|
107
|
+
*/
|
|
108
|
+
set range(visibleRange: TimeRange) {
|
|
109
|
+
const newRange = this.#computeActiveRange(visibleRange)
|
|
110
|
+
// Avoid redundant updates
|
|
111
|
+
if (
|
|
112
|
+
this.#activeRange[0] === newRange[0] &&
|
|
113
|
+
this.#activeRange[1] === newRange[1]
|
|
114
|
+
)
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
this.#activeRange = newRange
|
|
118
|
+
this.#update()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#updating: Promise<void> | null = null
|
|
122
|
+
#shouldRunAgain = false
|
|
123
|
+
|
|
124
|
+
async #update() {
|
|
125
|
+
// Perform update immediately. If multiple updates are requested while updating,
|
|
126
|
+
// only the latest one will run after the current finishes (skips intermediate ones).
|
|
127
|
+
if(this.#updating) {
|
|
128
|
+
this.#shouldRunAgain = true
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.#updating = this.#generateTiles()
|
|
133
|
+
await this.#updating
|
|
134
|
+
this.#updating = null
|
|
135
|
+
|
|
136
|
+
if(this.#shouldRunAgain) {
|
|
137
|
+
this.#shouldRunAgain = false
|
|
138
|
+
await this.#update()
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Returns the cached thumbnail (if any) for a given timestamp.
|
|
143
|
+
* @param time - The timestamp to retrieve the canvas for.
|
|
144
|
+
*/
|
|
145
|
+
getThumbnail(time: number) {
|
|
146
|
+
return this.#cache.get(time)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type TimeRange = [number, number]
|
|
151
|
+
|
|
152
|
+
interface FilmstripOptions {
|
|
153
|
+
frequency?: number
|
|
154
|
+
canvasSinkOptions?: CanvasSinkOptions
|
|
155
|
+
onChange: (tiles: {
|
|
156
|
+
time: number
|
|
157
|
+
canvas: WrappedCanvas
|
|
158
|
+
}[]) => void
|
|
159
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
import {Id, Hash} from "./basics.js"
|
|
3
|
+
|
|
4
|
+
export enum Kind {
|
|
5
|
+
Sequence,
|
|
6
|
+
Stack,
|
|
7
|
+
Clip,
|
|
8
|
+
Text,
|
|
9
|
+
Transition,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export enum Effect {
|
|
13
|
+
Crossfade,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export namespace Item {
|
|
17
|
+
export type Sequence = {
|
|
18
|
+
id: Id
|
|
19
|
+
kind: Kind.Sequence
|
|
20
|
+
children: Id[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Stack = {
|
|
24
|
+
id: Id
|
|
25
|
+
kind: Kind.Stack
|
|
26
|
+
children: Id[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type Clip = {
|
|
30
|
+
id: Id
|
|
31
|
+
kind: Kind.Clip
|
|
32
|
+
mediaHash: Hash
|
|
33
|
+
start: number
|
|
34
|
+
duration: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type Text = {
|
|
38
|
+
id: Id
|
|
39
|
+
kind: Kind.Text
|
|
40
|
+
content: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type Transition = {
|
|
44
|
+
id: Id
|
|
45
|
+
kind: Kind.Transition
|
|
46
|
+
effect: Effect.Crossfade
|
|
47
|
+
duration: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type Any = (
|
|
51
|
+
| Sequence
|
|
52
|
+
| Stack
|
|
53
|
+
| Clip
|
|
54
|
+
| Text
|
|
55
|
+
| Transition
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
import {Datafile} from "../utils/datafile.js"
|
|
3
|
+
|
|
4
|
+
export class Media {
|
|
5
|
+
duration = 0
|
|
6
|
+
constructor(public datafile: Datafile) {}
|
|
7
|
+
|
|
8
|
+
static async analyze(datafile: Datafile) {
|
|
9
|
+
const media = new this(datafile)
|
|
10
|
+
media.duration = 10
|
|
11
|
+
return media
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import {MapG} from "@e280/stz"
|
|
3
|
+
import {Hash} from "./basics.js"
|
|
4
|
+
import {Media} from "./media.js"
|
|
5
|
+
import {Resource} from "./resource.js"
|
|
6
|
+
import {Datafile} from "../utils/datafile.js"
|
|
7
|
+
|
|
8
|
+
export class ResourcePool {
|
|
9
|
+
#map = new MapG<Hash, Resource.Any>
|
|
10
|
+
|
|
11
|
+
/** store a media file (avoids duplicates via hash) */
|
|
12
|
+
async store(datafile: Datafile) {
|
|
13
|
+
const media = await Media.analyze(datafile)
|
|
14
|
+
const {hash} = media.datafile.checksum
|
|
15
|
+
const {filename, bytes} = media.datafile
|
|
16
|
+
|
|
17
|
+
if (this.#map.has(hash)) {
|
|
18
|
+
const alreadyExists = this.#map.require(hash)
|
|
19
|
+
alreadyExists.filename = filename
|
|
20
|
+
}
|
|
21
|
+
else
|
|
22
|
+
this.#map.set(hash, {kind: "media", filename, bytes})
|
|
23
|
+
|
|
24
|
+
return media
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import WaveSurfer from "wavesurfer.js"
|
|
2
|
+
|
|
3
|
+
import {context} from "../../context.js"
|
|
4
|
+
import {DecoderSource} from "../../driver/fns/schematic.js"
|
|
5
|
+
|
|
6
|
+
export class Waveform {
|
|
7
|
+
wavesurfer: WaveSurfer
|
|
8
|
+
|
|
9
|
+
constructor(peaks: number[], container: HTMLElement, duration: number) {
|
|
10
|
+
this.wavesurfer = WaveSurfer.create({
|
|
11
|
+
container,
|
|
12
|
+
waveColor: 'rgb(200, 0, 200)',
|
|
13
|
+
progressColor: 'rgb(100, 0, 100)',
|
|
14
|
+
barWidth: 10,
|
|
15
|
+
barRadius: 10,
|
|
16
|
+
barGap: 2,
|
|
17
|
+
peaks: [peaks],
|
|
18
|
+
duration
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static async init(source: DecoderSource, container: HTMLElement) {
|
|
23
|
+
const driver = await context.driver
|
|
24
|
+
const reader = driver.decode({ source }).audio.getReader()
|
|
25
|
+
|
|
26
|
+
const peaks: number[] = []
|
|
27
|
+
let buffer: number[] = []
|
|
28
|
+
const samplesPerPeak = 1024
|
|
29
|
+
const duration = await driver.getAudioDuration(source)
|
|
30
|
+
|
|
31
|
+
while (true) {
|
|
32
|
+
const {done, value: audioData} = await reader.read()
|
|
33
|
+
if (done) break
|
|
34
|
+
|
|
35
|
+
const frames = audioData.numberOfFrames
|
|
36
|
+
const plane = new Float32Array(frames)
|
|
37
|
+
audioData.copyTo(plane, {planeIndex: 0}) // Use left channel only
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < plane.length; i++) {
|
|
40
|
+
buffer.push(plane[i])
|
|
41
|
+
if (buffer.length >= samplesPerPeak) {
|
|
42
|
+
const chunk = buffer.splice(0, samplesPerPeak)
|
|
43
|
+
const min = Math.min(...chunk)
|
|
44
|
+
const max = Math.max(...chunk)
|
|
45
|
+
peaks.push(min, max)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
audioData.close()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return new Waveform(peaks, container, duration ?? 0)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// set zoom(value: number) {
|
|
56
|
+
// this.wavesurfer.zoom(value)
|
|
57
|
+
// }
|
|
58
|
+
|
|
59
|
+
set width(value: number) {
|
|
60
|
+
this.wavesurfer.setOptions({width: value})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
import {MapG} from "@e280/stz"
|
|
3
|
+
import {Id} from "../parts/basics.js"
|
|
4
|
+
import {Media} from "../parts/media.js"
|
|
5
|
+
import {Effect, Item, Kind} from "../parts/item.js"
|
|
6
|
+
|
|
7
|
+
export class O {
|
|
8
|
+
#nextId = 0
|
|
9
|
+
#items = new MapG<Id, Item.Any>()
|
|
10
|
+
|
|
11
|
+
#getId() {
|
|
12
|
+
return this.#nextId++
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
register(item: Item.Any) {
|
|
16
|
+
if (!this.#items.has(item.id))
|
|
17
|
+
this.#items.set(item.id, item)
|
|
18
|
+
return item.id
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get items() {
|
|
22
|
+
return [...this.#items.values()]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
sequence = (...items: Item.Any[]): Item.Sequence => ({
|
|
26
|
+
id: this.#getId(),
|
|
27
|
+
kind: Kind.Sequence,
|
|
28
|
+
children: items.map(item => this.register(item)),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
stack = (...items: Item.Any[]): Item.Stack => ({
|
|
32
|
+
id: this.#getId(),
|
|
33
|
+
kind: Kind.Stack,
|
|
34
|
+
children: items.map(item => this.register(item)),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
clip = (media: Media, start?: number, duration?: number): Item.Clip => ({
|
|
38
|
+
id: this.#getId(),
|
|
39
|
+
kind: Kind.Clip,
|
|
40
|
+
mediaHash: media.datafile.checksum.hash,
|
|
41
|
+
start: start ?? 0,
|
|
42
|
+
duration: duration ?? media.duration,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
text = (content: string): Item.Text => ({
|
|
46
|
+
id: this.#getId(),
|
|
47
|
+
kind: Kind.Text,
|
|
48
|
+
content,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
transition = {
|
|
52
|
+
crossfade: (duration: number): Item.Transition => ({
|
|
53
|
+
id: this.#getId(),
|
|
54
|
+
kind: Kind.Transition,
|
|
55
|
+
effect: Effect.Crossfade,
|
|
56
|
+
duration,
|
|
57
|
+
}),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
import {Omni} from "./omni.js"
|
|
3
|
+
import {dummyData} from "../utils/dummy-data.js"
|
|
4
|
+
|
|
5
|
+
//
|
|
6
|
+
// create an omni context
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
const omni = new Omni()
|
|
10
|
+
|
|
11
|
+
//
|
|
12
|
+
// load in some media resources
|
|
13
|
+
//
|
|
14
|
+
|
|
15
|
+
const {mediaA, mediaB} = await omni.load({
|
|
16
|
+
mediaA: dummyData(),
|
|
17
|
+
mediaB: dummyData(),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
//
|
|
21
|
+
// create a timeline
|
|
22
|
+
//
|
|
23
|
+
|
|
24
|
+
const timeline = omni.timeline(o => o.sequence(
|
|
25
|
+
o.clip(mediaA),
|
|
26
|
+
o.transition.crossfade(600),
|
|
27
|
+
o.stack(
|
|
28
|
+
o.clip(mediaB),
|
|
29
|
+
o.text("hello world"),
|
|
30
|
+
),
|
|
31
|
+
))
|
|
32
|
+
|
|
33
|
+
//
|
|
34
|
+
// log the timeline
|
|
35
|
+
//
|
|
36
|
+
|
|
37
|
+
console.log(JSON.stringify(timeline, undefined, " "))
|
|
38
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
import {O} from "./o.js"
|
|
3
|
+
import {Item} from "../parts/item.js"
|
|
4
|
+
import {Media} from "../parts/media.js"
|
|
5
|
+
import {TimelineFile} from "../parts/basics.js"
|
|
6
|
+
import {Datafile} from "../utils/datafile.js"
|
|
7
|
+
import {ResourcePool} from "../parts/resource-pool.js"
|
|
8
|
+
|
|
9
|
+
export class Omni {
|
|
10
|
+
resources = new ResourcePool()
|
|
11
|
+
|
|
12
|
+
load = async<S extends Record<string, Promise<Datafile>>>(spec: S) => {
|
|
13
|
+
return Object.fromEntries(await Promise.all(Object.entries(spec).map(
|
|
14
|
+
async([key, value]) => [key, await this.resources.store(await value)]
|
|
15
|
+
))) as {[K in keyof S]: Media}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
timeline = (fn: (o: O) => Item.Sequence): TimelineFile => {
|
|
19
|
+
const o = new O()
|
|
20
|
+
const sequence = fn(o)
|
|
21
|
+
return {
|
|
22
|
+
format: "timeline",
|
|
23
|
+
info: "https://omniclip.app/",
|
|
24
|
+
version: 0,
|
|
25
|
+
root: o.register(sequence),
|
|
26
|
+
items: o.items,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
import {Hex, Thumbprint} from "@e280/stz"
|
|
3
|
+
|
|
4
|
+
export class Checksum {
|
|
5
|
+
constructor(
|
|
6
|
+
public data: Uint8Array,
|
|
7
|
+
public bytes: Uint8Array,
|
|
8
|
+
public hash: string,
|
|
9
|
+
public nickname: string,
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
static async make(data: Uint8Array) {
|
|
13
|
+
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", data))
|
|
14
|
+
const hash = Hex.fromBytes(bytes)
|
|
15
|
+
const nickname = Thumbprint.sigil.fromBytes(bytes)
|
|
16
|
+
return new this(data, bytes, hash, nickname)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
import {Checksum} from "./checksum.js"
|
|
3
|
+
|
|
4
|
+
export class Datafile {
|
|
5
|
+
constructor(
|
|
6
|
+
public bytes: Uint8Array,
|
|
7
|
+
public filename: string,
|
|
8
|
+
public checksum: Checksum,
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
static async make(bytes: Uint8Array, name?: string) {
|
|
12
|
+
const checksum = await Checksum.make(bytes)
|
|
13
|
+
const filename = name ?? checksum.nickname
|
|
14
|
+
return new this(bytes, filename, checksum)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static async load(_path: string): Promise<Datafile> {
|
|
18
|
+
throw new Error("TODO implement")
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
package/x/context.d.ts
ADDED
package/x/context.js
ADDED
package/x/context.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../s/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAC,MAAM,oBAAoB,CAAA;AAEzC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,mCAAmC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE/E,MAAM,CAAC,MAAM,OAAO,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,EAAC,SAAS,EAAC,CAAC;CACjC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { context } from "../context.js";
|
|
2
|
+
import { waveformTest } from "./routines/waveform-test.js";
|
|
3
|
+
import { filmstripTest } from "./routines/filmstrip-test.js";
|
|
4
|
+
import { setupTranscodeTest } from "./routines/transcode-test.js";
|
|
5
|
+
const driver = await context.driver;
|
|
6
|
+
const results = document.querySelector(".results");
|
|
7
|
+
const fetchButton = document.querySelector(".fetch");
|
|
8
|
+
const importButton = document.querySelector(".import");
|
|
9
|
+
fetchButton?.addEventListener("click", startDemoFetch);
|
|
10
|
+
importButton?.addEventListener("click", startDemoImport);
|
|
11
|
+
waveformTest();
|
|
12
|
+
// hello world test
|
|
13
|
+
{
|
|
14
|
+
await driver.thread.work.hello();
|
|
15
|
+
if (driver.machina.count === 1)
|
|
16
|
+
console.log("✅ driver works");
|
|
17
|
+
else
|
|
18
|
+
console.error("❌ FAIL driver call didn't work");
|
|
19
|
+
}
|
|
20
|
+
// transcoding tests
|
|
21
|
+
async function startDemoImport() {
|
|
22
|
+
const [fileHandle] = await window.showOpenFilePicker();
|
|
23
|
+
const transcode = setupTranscodeTest(driver, fileHandle);
|
|
24
|
+
await filmstripTest(fileHandle);
|
|
25
|
+
run(transcode, fileHandle.name);
|
|
26
|
+
}
|
|
27
|
+
async function startDemoFetch() {
|
|
28
|
+
// which videos to run tests on
|
|
29
|
+
const videos = [
|
|
30
|
+
"/assets/temp/gl.mp4",
|
|
31
|
+
];
|
|
32
|
+
// running each test in sequence
|
|
33
|
+
for (const url of videos) {
|
|
34
|
+
const transcode = setupTranscodeTest(driver, "/assets/temp/gl.mp4");
|
|
35
|
+
run(transcode, url);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function run(transcode, label) {
|
|
39
|
+
// create result div
|
|
40
|
+
const div = document.createElement("div");
|
|
41
|
+
results.append(div);
|
|
42
|
+
// add video label
|
|
43
|
+
const p = document.createElement("p");
|
|
44
|
+
p.textContent = label;
|
|
45
|
+
div.append(p);
|
|
46
|
+
// add the canvas to dom
|
|
47
|
+
div.append(transcode.canvas);
|
|
48
|
+
// run the test
|
|
49
|
+
await transcode.run();
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=demo.bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo.bundle.js","sourceRoot":"","sources":["../../s/demo/demo.bundle.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAA;AACrC,OAAO,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAA;AACxD,OAAO,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAC,kBAAkB,EAAC,MAAM,8BAA8B,CAAA;AAE/D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAA;AACnC,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAE,CAAA;AAEnD,MAAM,WAAW,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;AACpD,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAsB,CAAA;AAE3E,WAAW,EAAE,gBAAgB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAA;AACtD,YAAY,EAAE,gBAAgB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAA;AAExD,YAAY,EAAE,CAAA;AAEd,mBAAmB;AACnB,CAAC;IACA,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;IAChC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;;QACxD,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAA;AACrD,CAAC;AAED,oBAAoB;AACpB,KAAK,UAAU,eAAe;IAE7B,MAAM,CAAC,UAAU,CAAC,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,CAAA;IACtD,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACxD,MAAM,aAAa,CAAC,UAAU,CAAC,CAAA;IAC/B,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,CAAA;AAChC,CAAC;AAED,KAAK,UAAU,cAAc;IAG5B,+BAA+B;IAC/B,MAAM,MAAM,GAAG;QACd,qBAAqB;KACrB,CAAA;IAED,gCAAgC;IAChC,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QAC1B,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAA;QACnE,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IACpB,CAAC;AACF,CAAC;AAED,KAAK,UAAU,GAAG,CAAC,SAAgD,EAAE,KAAa;IACjF,oBAAoB;IACpB,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IACzC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAEnB,kBAAkB;IAClB,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;IACrC,CAAC,CAAC,WAAW,GAAG,KAAK,CAAA;IACrB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IAEb,wBAAwB;IACxB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAE5B,eAAe;IACf,MAAM,SAAS,CAAC,GAAG,EAAE,CAAA;AACtB,CAAC"}
|