@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Przemysław Gałęzki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,2 +1,120 @@
|
|
|
1
|
-
|
|
2
|
-
#
|
|
1
|
+
|
|
2
|
+
# 🚧 Work In Progress
|
|
3
|
+
|
|
4
|
+
> **Note:** Omni Tools is under development. Expect breaking changes, evolving APIs, and experimental features.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 🎬 Omnitool
|
|
9
|
+
|
|
10
|
+
> Code-first video editing toolkit behind [Omniclip](https://omniclip.app) — build timelines, render videos, and automate workflows.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 🧱 Modular by Design
|
|
15
|
+
|
|
16
|
+
Omni Tools is a collection of composable utilities for working with Omniclip timelines — via code, JSON, or CLI.
|
|
17
|
+
|
|
18
|
+
- ✅ Define timelines in JSON or TypeScript
|
|
19
|
+
- ✅ Automate rendering with CLI tools
|
|
20
|
+
- ✅ Ideal for scripting, CI/CD, and AI-generated workflows
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 🚀 Quick Start
|
|
25
|
+
|
|
26
|
+
### Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm i @omnimedia/omnitool
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 📦 Example (Programmatic Timeline)
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { subtitle, crossfade, sequence, stack, video } from "@omni/tools"
|
|
38
|
+
|
|
39
|
+
const watermark = subtitle("omniclip")
|
|
40
|
+
const xfade = crossfade(500)
|
|
41
|
+
|
|
42
|
+
const timeline = sequence(
|
|
43
|
+
video("opening-credits.mp4"),
|
|
44
|
+
xfade,
|
|
45
|
+
stack(
|
|
46
|
+
video("skateboarding.mp4"),
|
|
47
|
+
watermark
|
|
48
|
+
),
|
|
49
|
+
xfade,
|
|
50
|
+
stack(
|
|
51
|
+
video("biking.mp4"),
|
|
52
|
+
watermark
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🧩 Timeline Format (Omni Timeline Format)
|
|
61
|
+
|
|
62
|
+
Every timeline is defined as a graph:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"format": "omni-timeline@1",
|
|
67
|
+
"root": "root-1",
|
|
68
|
+
"items": [
|
|
69
|
+
["root-1", ["sequence", { "children": ["video-1", "stack-1"] }]],
|
|
70
|
+
["video-1", ["video", { ... }]],
|
|
71
|
+
["stack-1", ["stack", { "children": ["text-1", "audio-1"] }]],
|
|
72
|
+
["text-1", ["text", { ... }]],
|
|
73
|
+
["audio-1", ["audio", { ... }]]
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Each item is a `[id, item]` pair. Items can reference others by ID, forming a directed graph of reusable, composable blocks.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 🖥 CLI Usage
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Build a timeline (manually or via AI)
|
|
86
|
+
omnitool build-template promo.json
|
|
87
|
+
|
|
88
|
+
# Validate structure
|
|
89
|
+
omnitool validate promo.json
|
|
90
|
+
|
|
91
|
+
# Render video
|
|
92
|
+
omnitool export promo.json --output final.mp4
|
|
93
|
+
|
|
94
|
+
# Batch render
|
|
95
|
+
omnitool batch-export ./projects/* --output-dir ./exports
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## ✅ Use Cases
|
|
101
|
+
|
|
102
|
+
- Render videos from scripts, templates, or AI
|
|
103
|
+
- Build and test timelines without opening the UI
|
|
104
|
+
- Generate video pipelines from code or prompts
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 📘 More to come
|
|
109
|
+
|
|
110
|
+
- `omnitool preview` – headless timeline viewer
|
|
111
|
+
- `omnitool optimize` – auto-fit timeline elements
|
|
112
|
+
- `omnitool ai` – native prompt-to-timeline generation
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 🧠 Learn More
|
|
117
|
+
|
|
118
|
+
Omni Tools powers AI agents, programmatic editing, and upcoming new version of omniclip video editor.
|
|
119
|
+
|
|
120
|
+
→ [Visit Omniclip](https://omniclip.app)
|
package/package.json
CHANGED
|
@@ -1,29 +1,58 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
2
|
+
"name": "@omnimedia/omnitool",
|
|
3
|
+
"version": "1.1.0-3",
|
|
4
|
+
"description": "open source video processing tools",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Przemysław Gałęzki",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "x/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"x",
|
|
11
|
+
"s"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "run-s _clean _tsc _ln _scute",
|
|
15
|
+
"start": "octo 'scute -vw' 'tsc -w' 'node --watch x/tests.test.js' 'http-server x'",
|
|
16
|
+
"_clean": "rm -rf x && mkdir x",
|
|
17
|
+
"_tsc": "tsc",
|
|
18
|
+
"_ln": "run-s _ln-s _ln-assets",
|
|
19
|
+
"_ln-s": "ln -s \"$(realpath s)\" x/s",
|
|
20
|
+
"_ln-assets": "ln -s \"$(realpath assets)\" x/assets",
|
|
21
|
+
"_scute": "scute -v",
|
|
22
|
+
"test": "node x/tests.test.js",
|
|
23
|
+
"test-debug": "node inspect x/tests.test.js"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@e280/science": "^0.0.5",
|
|
27
|
+
"@e280/scute": "^0.0.0-6",
|
|
28
|
+
"@types/node": "^24.0.14",
|
|
29
|
+
"http-server": "^14.1.1",
|
|
30
|
+
"npm-run-all": "^4.1.5",
|
|
31
|
+
"typescript": "^5.8.3"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@benev/slate": "^0.3.9",
|
|
35
|
+
"@e280/comrade": "^0.0.0-18",
|
|
36
|
+
"@e280/renraku": "^0.5.0-19",
|
|
37
|
+
"@e280/stz": "^0.0.0-22",
|
|
38
|
+
"comrade": "^0.0.3",
|
|
39
|
+
"mediabunny": "^1.1.1",
|
|
40
|
+
"mp4-muxer": "^5.2.1",
|
|
41
|
+
"pixi.js": "^8.10.1",
|
|
42
|
+
"wavesurfer.js": "^7.10.0",
|
|
43
|
+
"web-demuxer": "^2.3.8"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/omni-media/omnitool#readme",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/omni-media/omnitool.git"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/omni-media/omnitool/issues"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"tools",
|
|
55
|
+
"video",
|
|
56
|
+
"processing"
|
|
57
|
+
]
|
|
29
58
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export interface Effect {
|
|
2
|
+
start: number
|
|
3
|
+
end: number
|
|
4
|
+
duration: number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface TextEffect {
|
|
8
|
+
font: Font
|
|
9
|
+
content: string
|
|
10
|
+
color: string
|
|
11
|
+
size: number
|
|
12
|
+
style: FontStyle
|
|
13
|
+
align: TextAlign
|
|
14
|
+
rect: Rect
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SubtitleEffect {
|
|
18
|
+
content: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface FillEffect {
|
|
22
|
+
backgroundColor: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type FontStyle = "italic" | "bold" | "normal"
|
|
26
|
+
export type Font = "Arial" | "Lato"
|
|
27
|
+
export type TextAlign = "left" | "right" | "center"
|
|
28
|
+
|
|
29
|
+
export interface AudioClip extends Effect {
|
|
30
|
+
file: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface VideoClip extends Effect {
|
|
34
|
+
file: string
|
|
35
|
+
rect: Rect
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ImageEffect extends Effect {
|
|
39
|
+
file: string
|
|
40
|
+
rect: Rect
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface Transition {
|
|
44
|
+
type: TransitonType
|
|
45
|
+
duration: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type TransitonType = "crossfade" | "blur" | "fade"
|
|
49
|
+
|
|
50
|
+
export type AudioItem = ["audio", AudioClip]
|
|
51
|
+
export type VideoItem = ["video", VideoClip]
|
|
52
|
+
export type TransitionItem = ["transition", Transition]
|
|
53
|
+
export type SubtitleItem = ["subtitle", SubtitleEffect]
|
|
54
|
+
export type TextItem = ["text", Text]
|
|
55
|
+
export type ImageItem = ["image", ImageEffect]
|
|
56
|
+
export type FillItem = ["fill", FillEffect]
|
|
57
|
+
export type Sequence = ["sequence", {children: string[]}]
|
|
58
|
+
export type Stack = ["stack", {children: string[]}]
|
|
59
|
+
|
|
60
|
+
type Item =
|
|
61
|
+
| AudioItem
|
|
62
|
+
| VideoItem
|
|
63
|
+
| TransitionItem
|
|
64
|
+
| SubtitleItem
|
|
65
|
+
| TextItem
|
|
66
|
+
| ImageItem
|
|
67
|
+
| FillItem
|
|
68
|
+
| Sequence
|
|
69
|
+
| Stack
|
|
70
|
+
|
|
71
|
+
export type Items = [string, Item]
|
|
72
|
+
|
|
73
|
+
export interface Timeline {
|
|
74
|
+
root: string
|
|
75
|
+
items: Items[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface Rect {
|
|
79
|
+
width: number
|
|
80
|
+
height: number
|
|
81
|
+
scaleX: number
|
|
82
|
+
scaleY: number
|
|
83
|
+
position_on_canvas: {
|
|
84
|
+
x: number
|
|
85
|
+
y: number
|
|
86
|
+
}
|
|
87
|
+
rotation: number
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const defaultRect: Rect = {
|
|
91
|
+
width: 0,
|
|
92
|
+
height: 0,
|
|
93
|
+
scaleX: 0,
|
|
94
|
+
scaleY: 0,
|
|
95
|
+
position_on_canvas: {x: 0, y: 0},
|
|
96
|
+
rotation: 0,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const timeline: Timeline = {
|
|
100
|
+
root: "213",
|
|
101
|
+
items: [
|
|
102
|
+
[
|
|
103
|
+
"12321",
|
|
104
|
+
["video", { file: "", start: 0, end: 5, duration: 5, rect: defaultRect }],
|
|
105
|
+
],
|
|
106
|
+
],
|
|
107
|
+
}
|
package/s/context.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
import {context} from "../context.js"
|
|
3
|
+
import {waveformTest} from "./routines/waveform-test.js"
|
|
4
|
+
import {filmstripTest} from "./routines/filmstrip-test.js"
|
|
5
|
+
import {setupTranscodeTest} from "./routines/transcode-test.js"
|
|
6
|
+
|
|
7
|
+
const driver = await context.driver
|
|
8
|
+
const results = document.querySelector(".results")!
|
|
9
|
+
|
|
10
|
+
const fetchButton = document.querySelector(".fetch")
|
|
11
|
+
const importButton = document.querySelector(".import") as HTMLButtonElement
|
|
12
|
+
|
|
13
|
+
fetchButton?.addEventListener("click", startDemoFetch)
|
|
14
|
+
importButton?.addEventListener("click", startDemoImport)
|
|
15
|
+
|
|
16
|
+
waveformTest()
|
|
17
|
+
|
|
18
|
+
// hello world test
|
|
19
|
+
{
|
|
20
|
+
await driver.thread.work.hello()
|
|
21
|
+
if (driver.machina.count === 1) console.log("✅ driver works")
|
|
22
|
+
else console.error("❌ FAIL driver call didn't work")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// transcoding tests
|
|
26
|
+
async function startDemoImport()
|
|
27
|
+
{
|
|
28
|
+
const [fileHandle] = await window.showOpenFilePicker()
|
|
29
|
+
const transcode = setupTranscodeTest(driver, fileHandle)
|
|
30
|
+
await filmstripTest(fileHandle)
|
|
31
|
+
run(transcode, fileHandle.name)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function startDemoFetch()
|
|
35
|
+
{
|
|
36
|
+
|
|
37
|
+
// which videos to run tests on
|
|
38
|
+
const videos = [
|
|
39
|
+
"/assets/temp/gl.mp4",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
// running each test in sequence
|
|
43
|
+
for (const url of videos) {
|
|
44
|
+
const transcode = setupTranscodeTest(driver, "/assets/temp/gl.mp4")
|
|
45
|
+
run(transcode, url)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function run(transcode: ReturnType<typeof setupTranscodeTest>, label: string) {
|
|
50
|
+
// create result div
|
|
51
|
+
const div = document.createElement("div")
|
|
52
|
+
results.append(div)
|
|
53
|
+
|
|
54
|
+
// add video label
|
|
55
|
+
const p = document.createElement("p")
|
|
56
|
+
p.textContent = label
|
|
57
|
+
div.append(p)
|
|
58
|
+
|
|
59
|
+
// add the canvas to dom
|
|
60
|
+
div.append(transcode.canvas)
|
|
61
|
+
|
|
62
|
+
// run the test
|
|
63
|
+
await transcode.run()
|
|
64
|
+
}
|
package/s/demo/demo.css
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
@layer vars, basics, page;
|
|
3
|
+
|
|
4
|
+
@layer vars {
|
|
5
|
+
:root {
|
|
6
|
+
--link: cyan;
|
|
7
|
+
--color: #fffa;
|
|
8
|
+
--background: #111;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@layer basics {
|
|
13
|
+
* {
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 0;
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@layer page {
|
|
21
|
+
html {
|
|
22
|
+
font-size: 10px;
|
|
23
|
+
color: var(--color);
|
|
24
|
+
background: var(--background);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
body {
|
|
28
|
+
padding: 2em;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.results {
|
|
32
|
+
margin-top: 1em;
|
|
33
|
+
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
gap: 1em;
|
|
37
|
+
|
|
38
|
+
> div {
|
|
39
|
+
font-size: 1.5em;
|
|
40
|
+
border: 1px solid color-mix(in lch, transparent, var(--color) 20%);
|
|
41
|
+
padding: 1em;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
canvas {
|
|
45
|
+
width: 20em;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#filmstrip {
|
|
50
|
+
display: flex;
|
|
51
|
+
overflow-x: scroll;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {Filmstrip} from "../../timeline/parts/filmstrip.js"
|
|
2
|
+
|
|
3
|
+
export async function filmstripTest(fileHandle: FileSystemFileHandle) {
|
|
4
|
+
const rangeSlider = document.querySelector(".range") as HTMLInputElement
|
|
5
|
+
const rangeView = document.querySelector(".range-view")!
|
|
6
|
+
const rangeSizeSlider = document.querySelector(".range-size")! as HTMLInputElement
|
|
7
|
+
const frequencySlider = document.querySelector(".frequency")! as HTMLInputElement
|
|
8
|
+
const frequencyView = document.querySelector(".frequency-view")!
|
|
9
|
+
const container = document.querySelector("#filmstrip")!
|
|
10
|
+
const FPS_10 = 1000/10 / 1000
|
|
11
|
+
let rangeSize = 0.5
|
|
12
|
+
const filmstrip = await Filmstrip.init(
|
|
13
|
+
fileHandle,
|
|
14
|
+
{
|
|
15
|
+
onChange(tiles) {
|
|
16
|
+
// Sort by time (optional, for clean ordering)
|
|
17
|
+
const sorted = tiles.sort((a, b) => a.time - b.time)
|
|
18
|
+
// Clear previous thumbnails
|
|
19
|
+
container.replaceChildren(
|
|
20
|
+
...sorted.map(({ time, canvas }) => createLabeledCanvas(time, canvas.canvas as HTMLCanvasElement))
|
|
21
|
+
)
|
|
22
|
+
},
|
|
23
|
+
frequency: FPS_10,
|
|
24
|
+
canvasSinkOptions: {
|
|
25
|
+
width: 80,
|
|
26
|
+
height: 50,
|
|
27
|
+
fit: "fill"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
rangeSizeSlider.addEventListener("change", () => {
|
|
32
|
+
rangeSize = +rangeSizeSlider.value
|
|
33
|
+
const [start, end] = [+rangeSlider.value, +rangeSlider.value+rangeSize]
|
|
34
|
+
filmstrip.range = [start, end]
|
|
35
|
+
rangeView.textContent = `visible time range: [${start}, ${end}]`
|
|
36
|
+
})
|
|
37
|
+
rangeSlider.addEventListener("change", () => {
|
|
38
|
+
const [start, end] = [+rangeSlider.value, +rangeSlider.value+rangeSize]
|
|
39
|
+
filmstrip.range = [start, end]
|
|
40
|
+
rangeView.textContent = `visible time range: [${start}, ${end}]`
|
|
41
|
+
})
|
|
42
|
+
frequencySlider.addEventListener("change", () => {
|
|
43
|
+
filmstrip.frequency = 1000/+frequencySlider.value/1000
|
|
44
|
+
frequencyView.textContent = `frame every ${filmstrip.frequency.toFixed(3)} second (${frequencySlider.value} frames per second)`
|
|
45
|
+
})
|
|
46
|
+
filmstrip.range = [10, 10.5]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createLabeledCanvas(time: number, canvas: HTMLCanvasElement) {
|
|
50
|
+
const wrapper = document.createElement('div')
|
|
51
|
+
wrapper.style.position = 'relative'
|
|
52
|
+
wrapper.style.display = 'inline-block'
|
|
53
|
+
wrapper.style.marginRight = '4px'
|
|
54
|
+
wrapper.appendChild(canvas)
|
|
55
|
+
const label = document.createElement('div')
|
|
56
|
+
label.textContent = `${time.toFixed(2)}s`
|
|
57
|
+
label.style.position = 'absolute'
|
|
58
|
+
label.style.top = '2px'
|
|
59
|
+
label.style.right = '4px'
|
|
60
|
+
label.style.fontSize = '10px'
|
|
61
|
+
label.style.color = 'white'
|
|
62
|
+
label.style.background = 'rgba(0,0,0,0.6)'
|
|
63
|
+
label.style.padding = '2px 4px'
|
|
64
|
+
label.style.borderRadius = '4px'
|
|
65
|
+
label.style.pointerEvents = 'none'
|
|
66
|
+
wrapper.appendChild(label)
|
|
67
|
+
return wrapper
|
|
68
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {Driver} from "../../driver/driver.js"
|
|
2
|
+
import {DecoderSource} from "../../driver/fns/schematic.js"
|
|
3
|
+
|
|
4
|
+
export function setupTranscodeTest(driver: Driver, source: DecoderSource) {
|
|
5
|
+
const dimensions = {width: 1920, height: 1080}
|
|
6
|
+
|
|
7
|
+
const canvas = document.createElement("canvas")
|
|
8
|
+
canvas.width = dimensions.width
|
|
9
|
+
canvas.height = dimensions.height
|
|
10
|
+
const ctx = canvas.getContext("2d")
|
|
11
|
+
|
|
12
|
+
async function run() {
|
|
13
|
+
const readables = driver.decode({
|
|
14
|
+
source,
|
|
15
|
+
async onFrame(frame) {
|
|
16
|
+
const composed = await driver.composite([
|
|
17
|
+
{
|
|
18
|
+
kind: "image",
|
|
19
|
+
frame
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
kind: "text",
|
|
23
|
+
content: "omnitool",
|
|
24
|
+
fontSize: 50,
|
|
25
|
+
color: "green"
|
|
26
|
+
}
|
|
27
|
+
])
|
|
28
|
+
frame.close()
|
|
29
|
+
ctx?.drawImage(composed, 0, 0)
|
|
30
|
+
return composed
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
await driver.encode({
|
|
35
|
+
readables,
|
|
36
|
+
config: {
|
|
37
|
+
audio: {codec: "opus", bitrate: 128000},
|
|
38
|
+
video: {codec: "vp9", bitrate: 1000000}
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {canvas, run}
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {Waveform} from "../../timeline/parts/waveform.js"
|
|
2
|
+
|
|
3
|
+
export async function waveformTest() {
|
|
4
|
+
const container = document.querySelector(".waveform-demo") as HTMLElement
|
|
5
|
+
const widthSlider = document.querySelector(".width") as HTMLInputElement
|
|
6
|
+
const waveform = await Waveform.init("/assets/temp/gl.mp4", container)
|
|
7
|
+
|
|
8
|
+
widthSlider.addEventListener("change", () => {
|
|
9
|
+
const width = +widthSlider.value
|
|
10
|
+
waveform.width = width
|
|
11
|
+
})
|
|
12
|
+
}
|
|
@@ -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
|
+
|