@omnimedia/omnitool 1.0.0 → 1.1.0-10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -2
  3. package/package.json +59 -27
  4. package/s/_archive/types.ts +107 -0
  5. package/s/context.ts +7 -0
  6. package/s/demo/demo.bundle.ts +68 -0
  7. package/s/demo/demo.css +59 -0
  8. package/s/demo/routines/filmstrip-test.ts +68 -0
  9. package/s/demo/routines/load-video.ts +7 -0
  10. package/s/demo/routines/transcode-test.ts +46 -0
  11. package/s/demo/routines/transcriber-test.ts +34 -0
  12. package/s/demo/routines/transitions-test.ts +43 -0
  13. package/s/demo/routines/waveform-test.ts +12 -0
  14. package/s/driver/driver.test.ts +15 -0
  15. package/s/driver/driver.ts +124 -0
  16. package/s/driver/driver.worker.bundle.ts +7 -0
  17. package/s/driver/fns/host.ts +13 -0
  18. package/s/driver/fns/schematic.ts +106 -0
  19. package/s/driver/fns/work.ts +249 -0
  20. package/s/driver/parts/constants.ts +17 -0
  21. package/s/driver/parts/machina.ts +27 -0
  22. package/s/driver/utils/load-decoder-source.ts +12 -0
  23. package/s/driver/utils/sleep.ts +3 -0
  24. package/s/features/speech/transcribe/default-spec.ts +11 -0
  25. package/s/features/speech/transcribe/parts/load-pipe.ts +19 -0
  26. package/s/features/speech/transcribe/parts/prep-audio.ts +23 -0
  27. package/s/features/speech/transcribe/parts/transcribe.ts +70 -0
  28. package/s/features/speech/transcribe/transcriber.ts +46 -0
  29. package/s/features/speech/transcribe/types.ts +82 -0
  30. package/s/features/speech/transcribe/worker.bundle.ts +40 -0
  31. package/s/features/transition/parts/fragment.ts +24 -0
  32. package/s/features/transition/parts/types.ts +94 -0
  33. package/s/features/transition/parts/uniforms.ts +29 -0
  34. package/s/features/transition/parts/vertex.ts +31 -0
  35. package/s/features/transition/transition.ts +60 -0
  36. package/s/index.html.ts +58 -0
  37. package/s/index.ts +2 -39
  38. package/s/tests.test.ts +8 -0
  39. package/s/timeline/index.ts +15 -0
  40. package/s/timeline/parts/basics.ts +17 -0
  41. package/s/timeline/parts/compositor/export.ts +77 -0
  42. package/s/timeline/parts/compositor/parts/html-tree.ts +37 -0
  43. package/s/timeline/parts/compositor/parts/schedulers.ts +85 -0
  44. package/s/timeline/parts/compositor/parts/tree-builder.ts +184 -0
  45. package/s/timeline/parts/compositor/parts/webcodecs-tree.ts +30 -0
  46. package/s/timeline/parts/compositor/playback.ts +81 -0
  47. package/s/timeline/parts/compositor/samplers/html.ts +115 -0
  48. package/s/timeline/parts/compositor/samplers/webcodecs.ts +60 -0
  49. package/s/timeline/parts/filmstrip.ts +159 -0
  50. package/s/timeline/parts/item.ts +90 -0
  51. package/s/timeline/parts/media.ts +35 -0
  52. package/s/timeline/parts/resource-pool.ts +27 -0
  53. package/s/timeline/parts/resource.ts +11 -0
  54. package/s/timeline/parts/waveform.ts +62 -0
  55. package/s/timeline/sugar/builders.ts +102 -0
  56. package/s/timeline/sugar/o.ts +150 -0
  57. package/s/timeline/sugar/omni-test.ts +38 -0
  58. package/s/timeline/sugar/omni.ts +40 -0
  59. package/s/timeline/types.ts +29 -0
  60. package/s/timeline/utils/audio-stream.ts +15 -0
  61. package/s/timeline/utils/checksum.ts +20 -0
  62. package/s/timeline/utils/datafile.ts +21 -0
  63. package/s/timeline/utils/dummy-data.ts +7 -0
  64. package/s/timeline/utils/matrix.ts +33 -0
  65. package/s/timeline/utils/video-cursor.ts +40 -0
  66. package/s/tools/common/loader.ts +26 -0
  67. package/s/tools/common/transformer-pipeline.ts +26 -0
  68. package/s/tools/speech-recognition/common/model.ts +26 -0
  69. package/s/tools/speech-recognition/whisper/fns/host.ts +25 -0
  70. package/s/tools/speech-recognition/whisper/fns/schematic.ts +23 -0
  71. package/s/tools/speech-recognition/whisper/fns/work.ts +91 -0
  72. package/s/tools/speech-recognition/whisper/parts/types.ts +38 -0
  73. package/s/tools/speech-recognition/whisper/parts/worker.bundle.ts +7 -0
  74. package/s/tools/speech-recognition/whisper/tool.ts +70 -0
  75. package/x/context.d.ts +4 -0
  76. package/x/context.js +6 -0
  77. package/x/context.js.map +1 -0
  78. package/x/demo/demo.bundle.d.ts +1 -0
  79. package/x/demo/demo.bundle.js +55 -0
  80. package/x/demo/demo.bundle.js.map +1 -0
  81. package/x/demo/demo.bundle.min.js +120 -0
  82. package/x/demo/demo.bundle.min.js.map +7 -0
  83. package/x/demo/demo.css +59 -0
  84. package/x/demo/routines/filmstrip-test.d.ts +1 -0
  85. package/x/demo/routines/filmstrip-test.js +62 -0
  86. package/x/demo/routines/filmstrip-test.js.map +1 -0
  87. package/x/demo/routines/load-video.d.ts +1 -0
  88. package/x/demo/routines/load-video.js +6 -0
  89. package/x/demo/routines/load-video.js.map +1 -0
  90. package/x/demo/routines/transcode-test.d.ts +6 -0
  91. package/x/demo/routines/transcode-test.js +40 -0
  92. package/x/demo/routines/transcode-test.js.map +1 -0
  93. package/x/demo/routines/transcriber-test.d.ts +4 -0
  94. package/x/demo/routines/transcriber-test.js +33 -0
  95. package/x/demo/routines/transcriber-test.js.map +1 -0
  96. package/x/demo/routines/transitions-test.d.ts +5 -0
  97. package/x/demo/routines/transitions-test.js +35 -0
  98. package/x/demo/routines/transitions-test.js.map +1 -0
  99. package/x/demo/routines/waveform-test.d.ts +1 -0
  100. package/x/demo/routines/waveform-test.js +11 -0
  101. package/x/demo/routines/waveform-test.js.map +1 -0
  102. package/x/driver/driver.d.ts +20 -0
  103. package/x/driver/driver.js +104 -0
  104. package/x/driver/driver.js.map +1 -0
  105. package/x/driver/driver.test.d.ts +5 -0
  106. package/x/driver/driver.test.js +12 -0
  107. package/x/driver/driver.test.js.map +1 -0
  108. package/x/driver/driver.worker.bundle.d.ts +1 -0
  109. package/x/driver/driver.worker.bundle.js +4 -0
  110. package/x/driver/driver.worker.bundle.js.map +1 -0
  111. package/x/driver/driver.worker.bundle.min.js +3537 -0
  112. package/x/driver/driver.worker.bundle.min.js.map +7 -0
  113. package/x/driver/fns/host.d.ts +25 -0
  114. package/x/driver/fns/host.js +7 -0
  115. package/x/driver/fns/host.js.map +1 -0
  116. package/x/driver/fns/schematic.d.ts +84 -0
  117. package/x/driver/fns/schematic.js +2 -0
  118. package/x/driver/fns/schematic.js.map +1 -0
  119. package/x/driver/fns/work.d.ts +26 -0
  120. package/x/driver/fns/work.js +201 -0
  121. package/x/driver/fns/work.js.map +1 -0
  122. package/x/driver/parts/constants.d.ts +2 -0
  123. package/x/driver/parts/constants.js +17 -0
  124. package/x/driver/parts/constants.js.map +1 -0
  125. package/x/driver/parts/machina.d.ts +23 -0
  126. package/x/driver/parts/machina.js +14 -0
  127. package/x/driver/parts/machina.js.map +1 -0
  128. package/x/driver/utils/load-decoder-source.d.ts +3 -0
  129. package/x/driver/utils/load-decoder-source.js +11 -0
  130. package/x/driver/utils/load-decoder-source.js.map +1 -0
  131. package/x/driver/utils/sleep.d.ts +1 -0
  132. package/x/driver/utils/sleep.js +4 -0
  133. package/x/driver/utils/sleep.js.map +1 -0
  134. package/x/features/speech/transcribe/default-spec.d.ts +2 -0
  135. package/x/features/speech/transcribe/default-spec.js +8 -0
  136. package/x/features/speech/transcribe/default-spec.js.map +1 -0
  137. package/x/features/speech/transcribe/parts/load-pipe.d.ts +2 -0
  138. package/x/features/speech/transcribe/parts/load-pipe.js +13 -0
  139. package/x/features/speech/transcribe/parts/load-pipe.js.map +1 -0
  140. package/x/features/speech/transcribe/parts/prep-audio.d.ts +5 -0
  141. package/x/features/speech/transcribe/parts/prep-audio.js +21 -0
  142. package/x/features/speech/transcribe/parts/prep-audio.js.map +1 -0
  143. package/x/features/speech/transcribe/parts/transcribe.d.ts +5 -0
  144. package/x/features/speech/transcribe/parts/transcribe.js +56 -0
  145. package/x/features/speech/transcribe/parts/transcribe.js.map +1 -0
  146. package/x/features/speech/transcribe/transcriber.d.ts +5 -0
  147. package/x/features/speech/transcribe/transcriber.js +33 -0
  148. package/x/features/speech/transcribe/transcriber.js.map +1 -0
  149. package/x/features/speech/transcribe/types.d.ts +66 -0
  150. package/x/features/speech/transcribe/types.js.map +1 -0
  151. package/x/features/speech/transcribe/worker.bundle.d.ts +1 -0
  152. package/x/features/speech/transcribe/worker.bundle.js +33 -0
  153. package/x/features/speech/transcribe/worker.bundle.js.map +1 -0
  154. package/x/features/speech/transcribe/worker.bundle.min.js +2916 -0
  155. package/x/features/speech/transcribe/worker.bundle.min.js.map +7 -0
  156. package/x/features/transition/parts/fragment.d.ts +1 -0
  157. package/x/features/transition/parts/fragment.js +25 -0
  158. package/x/features/transition/parts/fragment.js.map +1 -0
  159. package/x/features/transition/parts/types.d.ts +23 -0
  160. package/x/features/transition/parts/types.js +2 -0
  161. package/x/features/transition/parts/types.js.map +1 -0
  162. package/x/features/transition/parts/uniforms.d.ts +31 -0
  163. package/x/features/transition/parts/uniforms.js +27 -0
  164. package/x/features/transition/parts/uniforms.js.map +1 -0
  165. package/x/features/transition/parts/vertex.d.ts +1 -0
  166. package/x/features/transition/parts/vertex.js +32 -0
  167. package/x/features/transition/parts/vertex.js.map +1 -0
  168. package/x/features/transition/transition.d.ts +5 -0
  169. package/x/features/transition/transition.js +50 -0
  170. package/x/features/transition/transition.js.map +1 -0
  171. package/x/index.d.ts +2 -9
  172. package/x/index.html +115 -0
  173. package/x/index.html.d.ts +2 -0
  174. package/x/index.html.js +52 -0
  175. package/x/index.html.js.map +1 -0
  176. package/x/index.js +2 -29
  177. package/x/index.js.map +1 -1
  178. package/x/tests.test.d.ts +1 -0
  179. package/x/tests.test.js +6 -0
  180. package/x/tests.test.js.map +1 -0
  181. package/x/timeline/index.d.ts +11 -0
  182. package/x/timeline/index.js +12 -0
  183. package/x/timeline/index.js.map +1 -0
  184. package/x/timeline/parts/basics.d.ts +12 -0
  185. package/x/timeline/parts/basics.js +2 -0
  186. package/x/timeline/parts/basics.js.map +1 -0
  187. package/x/timeline/parts/compositor/export.d.ts +9 -0
  188. package/x/timeline/parts/compositor/export.js +64 -0
  189. package/x/timeline/parts/compositor/export.js.map +1 -0
  190. package/x/timeline/parts/compositor/parts/html-tree.d.ts +3 -0
  191. package/x/timeline/parts/compositor/parts/html-tree.js +40 -0
  192. package/x/timeline/parts/compositor/parts/html-tree.js.map +1 -0
  193. package/x/timeline/parts/compositor/parts/schedulers.d.ts +15 -0
  194. package/x/timeline/parts/compositor/parts/schedulers.js +64 -0
  195. package/x/timeline/parts/compositor/parts/schedulers.js.map +1 -0
  196. package/x/timeline/parts/compositor/parts/tree-builder.d.ts +37 -0
  197. package/x/timeline/parts/compositor/parts/tree-builder.js +147 -0
  198. package/x/timeline/parts/compositor/parts/tree-builder.js.map +1 -0
  199. package/x/timeline/parts/compositor/parts/webcodecs-tree.d.ts +3 -0
  200. package/x/timeline/parts/compositor/parts/webcodecs-tree.js +28 -0
  201. package/x/timeline/parts/compositor/parts/webcodecs-tree.js.map +1 -0
  202. package/x/timeline/parts/compositor/playback.d.ts +19 -0
  203. package/x/timeline/parts/compositor/playback.js +71 -0
  204. package/x/timeline/parts/compositor/playback.js.map +1 -0
  205. package/x/timeline/parts/compositor/samplers/html.d.ts +3 -0
  206. package/x/timeline/parts/compositor/samplers/html.js +106 -0
  207. package/x/timeline/parts/compositor/samplers/html.js.map +1 -0
  208. package/x/timeline/parts/compositor/samplers/webcodecs.d.ts +2 -0
  209. package/x/timeline/parts/compositor/samplers/webcodecs.js +55 -0
  210. package/x/timeline/parts/compositor/samplers/webcodecs.js.map +1 -0
  211. package/x/timeline/parts/filmstrip.d.ts +39 -0
  212. package/x/timeline/parts/filmstrip.js +117 -0
  213. package/x/timeline/parts/filmstrip.js.map +1 -0
  214. package/x/timeline/parts/item.d.ts +68 -0
  215. package/x/timeline/parts/item.js +16 -0
  216. package/x/timeline/parts/item.js.map +1 -0
  217. package/x/timeline/parts/media.d.ts +10 -0
  218. package/x/timeline/parts/media.js +30 -0
  219. package/x/timeline/parts/media.js.map +1 -0
  220. package/x/timeline/parts/resource-pool.d.ts +7 -0
  221. package/x/timeline/parts/resource-pool.js +19 -0
  222. package/x/timeline/parts/resource-pool.js.map +1 -0
  223. package/x/timeline/parts/resource.d.ts +8 -0
  224. package/x/timeline/parts/resource.js +2 -0
  225. package/x/timeline/parts/resource.js.map +1 -0
  226. package/x/timeline/parts/waveform.d.ts +8 -0
  227. package/x/timeline/parts/waveform.js +51 -0
  228. package/x/timeline/parts/waveform.js.map +1 -0
  229. package/x/timeline/sugar/builders.d.ts +1 -0
  230. package/x/timeline/sugar/builders.js +104 -0
  231. package/x/timeline/sugar/builders.js.map +1 -0
  232. package/x/timeline/sugar/o.d.ts +32 -0
  233. package/x/timeline/sugar/o.js +114 -0
  234. package/x/timeline/sugar/o.js.map +1 -0
  235. package/x/timeline/sugar/omni-test.d.ts +1 -0
  236. package/x/timeline/sugar/omni-test.js +22 -0
  237. package/x/timeline/sugar/omni-test.js.map +1 -0
  238. package/x/timeline/sugar/omni.d.ts +14 -0
  239. package/x/timeline/sugar/omni.js +28 -0
  240. package/x/timeline/sugar/omni.js.map +1 -0
  241. package/x/timeline/types.d.ts +24 -0
  242. package/x/timeline/types.js +2 -0
  243. package/x/timeline/types.js.map +1 -0
  244. package/x/timeline/utils/audio-stream.d.ts +6 -0
  245. package/x/timeline/utils/audio-stream.js +17 -0
  246. package/x/timeline/utils/audio-stream.js.map +1 -0
  247. package/x/timeline/utils/checksum.d.ts +8 -0
  248. package/x/timeline/utils/checksum.js +21 -0
  249. package/x/timeline/utils/checksum.js.map +1 -0
  250. package/x/timeline/utils/datafile.d.ts +9 -0
  251. package/x/timeline/utils/datafile.js +20 -0
  252. package/x/timeline/utils/datafile.js.map +1 -0
  253. package/x/timeline/utils/dummy-data.d.ts +2 -0
  254. package/x/timeline/utils/dummy-data.js +3 -0
  255. package/x/timeline/utils/dummy-data.js.map +1 -0
  256. package/x/timeline/utils/matrix.d.ts +8 -0
  257. package/x/timeline/utils/matrix.js +26 -0
  258. package/x/timeline/utils/matrix.js.map +1 -0
  259. package/x/timeline/utils/video-cursor.d.ts +10 -0
  260. package/x/timeline/utils/video-cursor.js +36 -0
  261. package/x/timeline/utils/video-cursor.js.map +1 -0
  262. package/x/tools/common/loader.d.ts +19 -0
  263. package/x/tools/common/loader.js +18 -0
  264. package/x/tools/common/loader.js.map +1 -0
  265. package/x/tools/common/transformer-pipeline.d.ts +8 -0
  266. package/x/tools/common/transformer-pipeline.js +24 -0
  267. package/x/tools/common/transformer-pipeline.js.map +1 -0
  268. package/x/tools/speech-recognition/common/model.d.ts +14 -0
  269. package/x/tools/speech-recognition/common/model.js +16 -0
  270. package/x/tools/speech-recognition/common/model.js.map +1 -0
  271. package/x/tools/speech-recognition/whisper/fns/host.d.ts +13 -0
  272. package/x/tools/speech-recognition/whisper/fns/host.js +19 -0
  273. package/x/tools/speech-recognition/whisper/fns/host.js.map +1 -0
  274. package/x/tools/speech-recognition/whisper/fns/schematic.d.ts +19 -0
  275. package/x/tools/speech-recognition/whisper/fns/schematic.js +2 -0
  276. package/x/tools/speech-recognition/whisper/fns/schematic.js.map +1 -0
  277. package/x/tools/speech-recognition/whisper/fns/work.d.ts +12 -0
  278. package/x/tools/speech-recognition/whisper/fns/work.js +74 -0
  279. package/x/tools/speech-recognition/whisper/fns/work.js.map +1 -0
  280. package/x/tools/speech-recognition/whisper/parts/types.d.ts +31 -0
  281. package/x/tools/speech-recognition/whisper/parts/types.js +2 -0
  282. package/x/tools/speech-recognition/whisper/parts/types.js.map +1 -0
  283. package/x/tools/speech-recognition/whisper/parts/worker.bundle.d.ts +1 -0
  284. package/x/tools/speech-recognition/whisper/parts/worker.bundle.js +4 -0
  285. package/x/tools/speech-recognition/whisper/parts/worker.bundle.js.map +1 -0
  286. package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js +8 -0
  287. package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js.map +7 -0
  288. package/x/tools/speech-recognition/whisper/tool.d.ts +12 -0
  289. package/x/tools/speech-recognition/whisper/tool.js +63 -0
  290. package/x/tools/speech-recognition/whisper/tool.js.map +1 -0
  291. package/s/parts/compositor.ts +0 -5
  292. package/s/parts/export.ts +0 -5
  293. package/s/parts/video-decoder.ts +0 -27
  294. package/s/parts/video-encoder.ts +0 -15
  295. package/s/tools/generate-id.ts +0 -7
  296. package/s/tools/mp4boxjs/LICENSE.md +0 -24
  297. package/s/tools/mp4boxjs/demuxer.ts +0 -106
  298. package/s/tools/mp4boxjs/mp4box.adapter.ts +0 -148
  299. package/s/tools/mp4boxjs/mp4box.js +0 -8206
  300. package/s/types.ts +0 -10
  301. package/x/parts/compositor.d.ts +0 -4
  302. package/x/parts/compositor.js +0 -5
  303. package/x/parts/compositor.js.map +0 -1
  304. package/x/parts/export.d.ts +0 -7
  305. package/x/parts/export.js +0 -5
  306. package/x/parts/export.js.map +0 -1
  307. package/x/parts/video-decoder.d.ts +0 -8
  308. package/x/parts/video-decoder.js +0 -20
  309. package/x/parts/video-decoder.js.map +0 -1
  310. package/x/parts/video-encoder.d.ts +0 -6
  311. package/x/parts/video-encoder.js +0 -12
  312. package/x/parts/video-encoder.js.map +0 -1
  313. package/x/tools/generate-id.d.ts +0 -1
  314. package/x/tools/generate-id.js +0 -8
  315. package/x/tools/generate-id.js.map +0 -1
  316. package/x/tools/mp4boxjs/demuxer.d.ts +0 -24
  317. package/x/tools/mp4boxjs/demuxer.js +0 -88
  318. package/x/tools/mp4boxjs/demuxer.js.map +0 -1
  319. package/x/tools/mp4boxjs/mp4box.adapter.d.ts +0 -128
  320. package/x/tools/mp4boxjs/mp4box.adapter.js +0 -11
  321. package/x/tools/mp4boxjs/mp4box.adapter.js.map +0 -1
  322. package/x/types.d.ts +0 -7
  323. package/x/types.js.map +0 -1
  324. /package/x/{types.js → features/speech/transcribe/types.js} +0 -0
@@ -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,90 @@
1
+
2
+ import {Id, Hash} from "./basics.js"
3
+ import {Transform} from "../types.js"
4
+
5
+ export enum Kind {
6
+ Sequence,
7
+ Stack,
8
+ Video,
9
+ Audio,
10
+ Text,
11
+ Gap,
12
+ Spatial,
13
+ Transition,
14
+ }
15
+
16
+ export enum Effect {
17
+ Crossfade,
18
+ }
19
+
20
+ export namespace Item {
21
+ export type Spatial = {
22
+ id: Id
23
+ kind: Kind.Spatial
24
+ transform: Transform
25
+ }
26
+
27
+ export type Gap = {
28
+ id: Id
29
+ kind: Kind.Gap
30
+ duration: number
31
+ }
32
+
33
+ export type Sequence = {
34
+ id: Id
35
+ kind: Kind.Sequence
36
+ childrenIds: Id[]
37
+ spatialId?: Id
38
+ }
39
+
40
+ export type Stack = {
41
+ id: Id
42
+ kind: Kind.Stack
43
+ childrenIds: Id[]
44
+ spatialId?: Id
45
+ }
46
+
47
+ export type Video = {
48
+ id: Id
49
+ kind: Kind.Video
50
+ mediaHash: Hash
51
+ start: number
52
+ duration: number
53
+ spatialId?: Id
54
+ }
55
+
56
+ export type Audio = {
57
+ id: Id
58
+ kind: Kind.Audio
59
+ mediaHash: Hash
60
+ start: number
61
+ duration: number
62
+ }
63
+
64
+ export type Text = {
65
+ id: Id
66
+ kind: Kind.Text
67
+ content: string
68
+ spatialId?: Id
69
+ color: string
70
+ }
71
+
72
+ export type Transition = {
73
+ id: Id
74
+ kind: Kind.Transition
75
+ effect: Effect.Crossfade
76
+ duration: number
77
+ }
78
+
79
+ export type Any = (
80
+ | Sequence
81
+ | Stack
82
+ | Video
83
+ | Audio
84
+ | Text
85
+ | Gap
86
+ | Transition
87
+ | Spatial
88
+ )
89
+ }
90
+
@@ -0,0 +1,35 @@
1
+
2
+ import {ALL_FORMATS, Input} from "mediabunny"
3
+
4
+ import {Datafile} from "../utils/datafile.js"
5
+ import {DecoderSource} from "../../driver/fns/schematic.js"
6
+ import {loadDecoderSource} from "../../driver/utils/load-decoder-source.js"
7
+
8
+ export class Media {
9
+ duration = 0
10
+ hasVideo = false
11
+ hasAudio = false
12
+
13
+ constructor(public datafile: Datafile) {}
14
+
15
+ static async analyze(datafile: Datafile) {
16
+ const media = new this(datafile)
17
+ media.duration = 10
18
+ const {video, audio} = await this.#has("/assets/temp/gl.mp4")
19
+ media.hasAudio = audio
20
+ media.hasVideo = video
21
+ return media
22
+ }
23
+
24
+ static async #has(source: DecoderSource) {
25
+ const input = new Input({
26
+ formats: ALL_FORMATS,
27
+ source: await loadDecoderSource(source)
28
+ })
29
+ return {
30
+ audio: !!(await input.getPrimaryAudioTrack()),
31
+ video: !!(await input.getPrimaryVideoTrack())
32
+ }
33
+ }
34
+ }
35
+
@@ -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,11 @@
1
+
2
+ export namespace Resource {
3
+ export type Media = {
4
+ kind: "media"
5
+ filename: string
6
+ bytes: Uint8Array
7
+ }
8
+
9
+ export type Any = Media
10
+ }
11
+
@@ -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.decodeAudio({source}).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,102 @@
1
+ // import {O} from "./o.js"
2
+ // import {Id} from "../parts/basics.js"
3
+ // import {Item} from "../parts/item.js"
4
+ //
5
+ // export class TimelineItem {
6
+ // public readonly id: Id
7
+ //
8
+ // constructor(public item: Item.Any) {
9
+ // this.id = item.id
10
+ // }
11
+ //
12
+ // toJSON() {
13
+ // return {
14
+ // ...this.item
15
+ // }
16
+ // }
17
+ // }
18
+ //
19
+ // abstract class VisualItem extends TimelineItem {
20
+ // abstract spatial(spatial: Spatial): TimelineItem
21
+ // }
22
+ //
23
+ // export class Stack extends VisualItem {
24
+ // constructor(private o: O, public item: Item.Stack) {
25
+ // super(item)
26
+ // }
27
+ //
28
+ // spatial(spatial: Spatial) {
29
+ // this.item.spatialId = spatial.item.id
30
+ // return this
31
+ // }
32
+ //
33
+ // addChildren(fn: (o: O) => TimelineItem | TimelineItem[]) {
34
+ // const result = fn(this.o)
35
+ // const items = Array.isArray(result) ? result : [result]
36
+ // this.item.childrenIds.push(...items.map(c => c.item.id))
37
+ // return this
38
+ // }
39
+ // }
40
+ //
41
+ // export class Spatial extends TimelineItem {
42
+ // constructor(public item: Item.Spatial) {super(item)}
43
+ // }
44
+ //
45
+ // export class Gap extends TimelineItem {
46
+ // constructor(public item: Item.Gap) {super(item)}
47
+ // }
48
+ //
49
+ // export class Audio extends TimelineItem {
50
+ // constructor(public item: Item.Audio) {super(item)}
51
+ // }
52
+ //
53
+ // export class Video extends VisualItem {
54
+ // constructor(public item: Item.Video) {
55
+ // super(item)
56
+ // }
57
+ //
58
+ // spatial(spatial: Spatial) {
59
+ // this.item.spatialId = spatial.item.id
60
+ // return this
61
+ // }
62
+ // }
63
+ //
64
+ // export class Text extends VisualItem {
65
+ // constructor(public item: Item.Text) {
66
+ // super(item)
67
+ // }
68
+ //
69
+ // color(color: string) {
70
+ // this.item.color = color
71
+ // return this
72
+ // }
73
+ //
74
+ // spatial(spatial: Spatial) {
75
+ // this.item.spatialId = spatial.item.id
76
+ // return this
77
+ // }
78
+ // }
79
+ //
80
+ // export class Sequence extends VisualItem {
81
+ // constructor(private o: O, public item: Item.Sequence) {
82
+ // super(item)
83
+ // }
84
+ //
85
+ // spatial(spatial: Spatial) {
86
+ // this.item.spatialId = spatial.item.id
87
+ // return this
88
+ // }
89
+ //
90
+ // addChildren(fn: (o: O) => TimelineItem | TimelineItem[]) {
91
+ // const result = fn(this.o)
92
+ // const items = Array.isArray(result) ? result : [result]
93
+ // this.item.childrenIds.push(...items.map(c => c.item.id))
94
+ // return this
95
+ // }
96
+ // }
97
+ //
98
+ // export class Transition extends TimelineItem {
99
+ // constructor(public item: Item.Transition) {
100
+ // super(item)
101
+ // }
102
+ // }
@@ -0,0 +1,150 @@
1
+
2
+ import {Media} from "../parts/media.js"
3
+ import {Id, TimelineFile} from "../parts/basics.js"
4
+ import {Effect, Item, Kind} from "../parts/item.js"
5
+ import {Transform, TransformOptions, Vec2} from "../types.js"
6
+
7
+ export class O {
8
+ #nextId = 0
9
+
10
+ constructor(public state: {project: TimelineFile}) {}
11
+
12
+ require<T extends Item.Any>(id: Id): T {
13
+ const item = this.state.project.items.find(item => item.id === id)
14
+ return item as T
15
+ }
16
+
17
+ #getId() {
18
+ return this.#nextId++
19
+ }
20
+
21
+ #mutate(fn: (project: TimelineFile) => TimelineFile) {
22
+ this.state.project = fn(this.state.project)
23
+ }
24
+
25
+ spatial = (transform: Transform): Item.Spatial => {
26
+ const item: Item.Spatial = {
27
+ id: this.#getId(),
28
+ kind: Kind.Spatial,
29
+ transform
30
+ }
31
+ return item
32
+ }
33
+
34
+ sequence = (...items: Item.Any[]): Item.Any => {
35
+ const item = {
36
+ id: this.#getId(),
37
+ kind: Kind.Sequence,
38
+ childrenIds: items.map(item => item.id)
39
+ } as Item.Sequence
40
+
41
+ this.#mutate(state => {
42
+ state.items.push(item, ...items)
43
+ return state
44
+ })
45
+
46
+ return item
47
+ }
48
+
49
+ stack = (...items: Item.Any[]): Item.Any => {
50
+ const item = {
51
+ kind: Kind.Stack,
52
+ id: this.#getId(),
53
+ childrenIds: items.map(item => item.id)
54
+ } as Item.Stack
55
+
56
+ this.#mutate(state => {
57
+ state.items.push(item, ...items)
58
+ return state
59
+ })
60
+
61
+ return item
62
+ }
63
+
64
+ video = (
65
+ media: Media,
66
+ options?: {
67
+ start?: number,
68
+ duration?: number
69
+ }): Item.Video => {
70
+
71
+ if(!media.hasVideo)
72
+ throw new Error(`Video clip error: media "${media.datafile.filename}" has no video track.`)
73
+
74
+ const item: Item.Video = {
75
+ kind: Kind.Video,
76
+ id: this.#getId(),
77
+ mediaHash: media.datafile.checksum.hash,
78
+ start: options?.start ?? 0,
79
+ duration: options?.duration ?? media.duration
80
+ }
81
+
82
+ return item
83
+ }
84
+
85
+ audio = (
86
+ media: Media,
87
+ options?: {
88
+ start?: number,
89
+ duration?: number
90
+ }): Item.Audio => {
91
+
92
+ if(!media.hasAudio)
93
+ throw new Error(`Audio clip error: media "${media.datafile.filename}" has no audio track.`)
94
+
95
+ const item: Item.Audio = {
96
+ kind: Kind.Audio,
97
+ id: this.#getId(),
98
+ mediaHash: media.datafile.checksum.hash,
99
+ start: options?.start ?? 0,
100
+ duration: options?.duration ?? media.duration
101
+ }
102
+
103
+ return item
104
+ }
105
+
106
+ text = (content: string): Item.Text => ({
107
+ id: this.#getId(),
108
+ content,
109
+ kind: Kind.Text,
110
+ color: "#FFFFF"
111
+ })
112
+
113
+ gap = (duration: number): Item.Gap => ({
114
+ id: this.#getId(),
115
+ kind: Kind.Gap,
116
+ duration
117
+ })
118
+
119
+ transition = {
120
+ crossfade: (duration: number): Item.Transition => ({
121
+ id: this.#getId(),
122
+ kind: Kind.Transition,
123
+ effect: Effect.Crossfade,
124
+ duration,
125
+ }),
126
+ }
127
+
128
+ transform = (options?: TransformOptions): Transform => {
129
+ const position: Vec2 = [
130
+ options?.position?.[0] ?? 0,
131
+ options?.position?.[1] ?? 0
132
+ ]
133
+ const scale: Vec2 = [
134
+ options?.scale?.[0] ?? 1,
135
+ options?.scale?.[1] ?? 1
136
+ ]
137
+ const rotation = options?.rotation ?? 0
138
+ return [position, scale, rotation]
139
+ }
140
+
141
+ addChildren(parent: Item.Stack | Item.Sequence, ...items: Item.Any[]) {
142
+ this.#mutate(state => {
143
+ const parentItem = state.items.find(({id}) => id === parent.id) as Item.Stack
144
+ parentItem.childrenIds.push(...items.map(item => item.id))
145
+ state.items.push(...items)
146
+ return state
147
+ })
148
+ }
149
+ }
150
+
@@ -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.video(mediaA),
26
+ o.transition.crossfade(600),
27
+ o.stack(
28
+ o.video(mediaB),
29
+ o.text("hello world"),
30
+ ),
31
+ ))
32
+
33
+ //
34
+ // log the timeline
35
+ //
36
+
37
+ console.log(JSON.stringify(timeline, undefined, " "))
38
+