@editframe/elements 0.5.0-beta.8 → 0.5.0-beta.9

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 (59) hide show
  1. package/dist/{editor/util/EncodedAsset → lib/av}/EncodedAsset.mjs +24 -22
  2. package/dist/{editor/util → lib/av}/MP4File.mjs +50 -51
  3. package/dist/{util → lib/util}/memoize.mjs +1 -2
  4. package/dist/packages/elements/src/EF_FRAMEGEN.mjs +183 -0
  5. package/dist/{elements → packages/elements}/src/elements/EFAudio.mjs +1 -4
  6. package/dist/{elements → packages/elements}/src/elements/EFCaptions.mjs +5 -7
  7. package/dist/{elements → packages/elements}/src/elements/EFImage.mjs +2 -3
  8. package/dist/{elements → packages/elements}/src/elements/EFMedia.mjs +18 -27
  9. package/dist/{elements → packages/elements}/src/elements/EFSourceMixin.mjs +5 -7
  10. package/dist/{elements → packages/elements}/src/elements/EFTemporal.mjs +2 -15
  11. package/dist/{elements → packages/elements}/src/elements/EFTimegroup.mjs +32 -43
  12. package/dist/{elements → packages/elements}/src/elements/EFVideo.mjs +8 -30
  13. package/dist/{elements → packages/elements}/src/elements/EFWaveform.mjs +1 -2
  14. package/dist/{elements → packages/elements}/src/elements/FetchMixin.mjs +4 -6
  15. package/dist/{elements → packages/elements}/src/gui/EFFilmstrip.mjs +10 -22
  16. package/dist/{elements → packages/elements}/src/gui/EFWorkbench.mjs +4 -25
  17. package/dist/packages/elements/src/gui/TWMixin.css.mjs +4 -0
  18. package/dist/style.css +13 -4
  19. package/package.json +7 -2
  20. package/dist/elements/src/EF_FRAMEGEN.mjs +0 -130
  21. package/dist/elements/src/gui/TWMixin.css.mjs +0 -4
  22. package/dist/util/awaitAnimationFrame.mjs +0 -11
  23. package/docker-compose.yaml +0 -17
  24. package/src/EF_FRAMEGEN.ts +0 -208
  25. package/src/EF_INTERACTIVE.ts +0 -2
  26. package/src/elements/CrossUpdateController.ts +0 -18
  27. package/src/elements/EFAudio.ts +0 -42
  28. package/src/elements/EFCaptions.ts +0 -202
  29. package/src/elements/EFImage.ts +0 -70
  30. package/src/elements/EFMedia.ts +0 -395
  31. package/src/elements/EFSourceMixin.ts +0 -57
  32. package/src/elements/EFTemporal.ts +0 -246
  33. package/src/elements/EFTimegroup.browsertest.ts +0 -360
  34. package/src/elements/EFTimegroup.ts +0 -394
  35. package/src/elements/EFTimeline.ts +0 -13
  36. package/src/elements/EFVideo.ts +0 -114
  37. package/src/elements/EFWaveform.ts +0 -407
  38. package/src/elements/FetchMixin.ts +0 -18
  39. package/src/elements/TimegroupController.ts +0 -25
  40. package/src/elements/buildLitFixture.ts +0 -13
  41. package/src/elements/durationConverter.ts +0 -6
  42. package/src/elements/parseTimeToMs.ts +0 -10
  43. package/src/elements/util.ts +0 -24
  44. package/src/gui/EFFilmstrip.ts +0 -702
  45. package/src/gui/EFWorkbench.ts +0 -242
  46. package/src/gui/TWMixin.css +0 -3
  47. package/src/gui/TWMixin.ts +0 -27
  48. package/src/util.d.ts +0 -1
  49. /package/dist/{editor → lib/av}/msToTimeCode.mjs +0 -0
  50. /package/dist/{util → lib/util}/awaitMicrotask.mjs +0 -0
  51. /package/dist/{elements → packages/elements}/src/EF_INTERACTIVE.mjs +0 -0
  52. /package/dist/{elements → packages/elements}/src/elements/CrossUpdateController.mjs +0 -0
  53. /package/dist/{elements → packages/elements}/src/elements/EFTimeline.mjs +0 -0
  54. /package/dist/{elements → packages/elements}/src/elements/TimegroupController.mjs +0 -0
  55. /package/dist/{elements → packages/elements}/src/elements/durationConverter.mjs +0 -0
  56. /package/dist/{elements → packages/elements}/src/elements/parseTimeToMs.mjs +0 -0
  57. /package/dist/{elements → packages/elements}/src/elements/util.mjs +0 -0
  58. /package/dist/{elements → packages/elements}/src/elements.mjs +0 -0
  59. /package/dist/{elements → packages/elements}/src/gui/TWMixin.mjs +0 -0
@@ -1,130 +0,0 @@
1
- import { shallowGetTimegroups, deepGetElementsWithFrameTasks } from "./elements/EFTemporal.mjs";
2
- import { awaitAnimationFrame } from "../../util/awaitAnimationFrame.mjs";
3
- import { awaitMicrotask } from "../../util/awaitMicrotask.mjs";
4
- class TriggerCanvas {
5
- constructor() {
6
- this.canvas = document.createElement("canvas");
7
- this.canvas.width = 1;
8
- this.canvas.height = 1;
9
- Object.assign(this.canvas.style, {
10
- position: "absolute",
11
- top: "0px",
12
- left: "0px",
13
- width: `1px`,
14
- height: `1px`,
15
- zIndex: "100000"
16
- });
17
- document.body.prepend(this.canvas);
18
- this.ctx = this.canvas.getContext("2d");
19
- }
20
- trigger() {
21
- console.log("Triggering");
22
- this.ctx.fillStyle = "rgba(50, 0, 0, .8)";
23
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
24
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
25
- }
26
- }
27
- if (window.EF_FRAMEGEN !== void 0) {
28
- const EF_FRAMEGEN = window.EF_FRAMEGEN;
29
- EF_FRAMEGEN.onRender((renderId, traceCarrier, renderOptions) => {
30
- const crashOnUnhandledError = (error) => {
31
- EF_FRAMEGEN.error(renderId, error);
32
- };
33
- window.addEventListener("error", (error) => {
34
- console.warn("Crashing due to unhandled error", error);
35
- crashOnUnhandledError(error.message);
36
- });
37
- window.addEventListener("unhandledrejection", (error) => {
38
- console.warn("Crashing due to unhandled rejection", error);
39
- crashOnUnhandledError(error.reason);
40
- });
41
- const triggerCanvas = new TriggerCanvas();
42
- EF_FRAMEGEN.onPaint(async () => {
43
- triggerCanvas.trigger();
44
- await awaitAnimationFrame();
45
- await awaitAnimationFrame();
46
- EF_FRAMEGEN.didPaint(renderId);
47
- });
48
- const workbench = document.querySelector("ef-workbench");
49
- workbench.rendering = true;
50
- const timegroups = shallowGetTimegroups(workbench);
51
- const temporals = deepGetElementsWithFrameTasks(workbench);
52
- const firstGroup = timegroups[0];
53
- if (!firstGroup) {
54
- throw new Error("No temporal elements found");
55
- }
56
- firstGroup.currentTimeMs = renderOptions.encoderOptions.fromMs;
57
- const frameDurationMs = 1e3 / renderOptions.encoderOptions.video.framerate;
58
- const initialBusyTasks = Promise.all(
59
- temporals.map((temporal) => temporal.frameTask).map((task) => task.taskComplete)
60
- );
61
- const frameBox = document.createElement("div");
62
- Object.assign(frameBox.style, {
63
- width: "200px",
64
- height: "100px",
65
- font: "30px Arial",
66
- backgroundColor: "white",
67
- position: "absolute",
68
- top: "0px",
69
- left: "0px",
70
- zIndex: "100000"
71
- });
72
- let time = 0;
73
- console.log("onRender", renderId, traceCarrier, renderOptions);
74
- const audioBufferPromise = firstGroup.renderAudio(
75
- renderOptions.encoderOptions.alignedFromUs / 1e3,
76
- renderOptions.encoderOptions.alignedToUs / 1e3
77
- // renderOptions.encoderOptions.fromMs,
78
- // renderOptions.encoderOptions.toMs,
79
- );
80
- EF_FRAMEGEN.onBegin(async (traceCarrier2, frame, isLast) => {
81
- time = firstGroup.currentTimeMs = renderOptions.encoderOptions.fromMs + frame * frameDurationMs;
82
- console.log("FRAME #", frame);
83
- frameBox.innerHTML = `
84
- <div>Frame #${frame}</div>
85
- <div>${time.toFixed(4)}</div>
86
- `;
87
- await initialBusyTasks;
88
- console.log("TIME", time.toFixed(4));
89
- await awaitMicrotask();
90
- console.log("After microtask");
91
- const now = performance.now();
92
- console.log(`frame:${frame} Awaiting busyTasks`);
93
- await Promise.all(
94
- temporals.map((temporal) => {
95
- console.log(
96
- "Awaiting",
97
- temporal.tagName,
98
- temporal.frameTask.status,
99
- temporal.frameTask.taskComplete
100
- );
101
- return temporal.frameTask;
102
- }).map((task) => task.taskComplete)
103
- );
104
- console.log(
105
- `frame:${frame} All tasks complete ${performance.now() - now}ms`
106
- );
107
- await awaitAnimationFrame();
108
- await awaitAnimationFrame();
109
- triggerCanvas.trigger();
110
- if (isLast) {
111
- const renderedAudio = await audioBufferPromise;
112
- const channelCount = renderedAudio.numberOfChannels;
113
- const interleavedSamples = new Float32Array(
114
- channelCount * renderedAudio.length
115
- );
116
- for (let i = 0; i < renderedAudio.length; i++) {
117
- for (let j = 0; j < channelCount; j++) {
118
- interleavedSamples.set(
119
- renderedAudio.getChannelData(j).slice(i, i + 1),
120
- i * channelCount + j
121
- );
122
- }
123
- }
124
- EF_FRAMEGEN.frameReady(renderId, frame, interleavedSamples.buffer);
125
- } else {
126
- EF_FRAMEGEN.frameReady(renderId, frame, new Float32Array(0).buffer);
127
- }
128
- });
129
- });
130
- }
@@ -1,4 +0,0 @@
1
- const twStyle = "/*\n! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com\n*//*\n1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)\n2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)\n*/\n\n*,\n::before,\n::after {\n box-sizing: border-box; /* 1 */\n border-width: 0; /* 2 */\n border-style: solid; /* 2 */\n border-color: #e5e7eb; /* 2 */\n}\n\n::before,\n::after {\n --tw-content: '';\n}\n\n/*\n1. Use a consistent sensible line-height in all browsers.\n2. Prevent adjustments of font size after orientation changes in iOS.\n3. Use a more readable tab size.\n4. Use the user's configured `sans` font-family by default.\n5. Use the user's configured `sans` font-feature-settings by default.\n6. Use the user's configured `sans` font-variation-settings by default.\n7. Disable tap highlights on iOS\n*/\n\nhtml,\n:host {\n line-height: 1.5; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n -moz-tab-size: 4; /* 3 */\n -o-tab-size: 4;\n tab-size: 4; /* 3 */\n font-family: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"; /* 4 */\n font-feature-settings: normal; /* 5 */\n font-variation-settings: normal; /* 6 */\n -webkit-tap-highlight-color: transparent; /* 7 */\n}\n\n/*\n1. Remove the margin in all browsers.\n2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.\n*/\n\nbody {\n margin: 0; /* 1 */\n line-height: inherit; /* 2 */\n}\n\n/*\n1. Add the correct height in Firefox.\n2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)\n3. Ensure horizontal rules are visible by default.\n*/\n\nhr {\n height: 0; /* 1 */\n color: inherit; /* 2 */\n border-top-width: 1px; /* 3 */\n}\n\n/*\nAdd the correct text decoration in Chrome, Edge, and Safari.\n*/\n\nabbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n}\n\n/*\nRemove the default font size and weight for headings.\n*/\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n font-size: inherit;\n font-weight: inherit;\n}\n\n/*\nReset links to optimize for opt-in styling instead of opt-out.\n*/\n\na {\n color: inherit;\n text-decoration: inherit;\n}\n\n/*\nAdd the correct font weight in Edge and Safari.\n*/\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/*\n1. Use the user's configured `mono` font-family by default.\n2. Use the user's configured `mono` font-feature-settings by default.\n3. Use the user's configured `mono` font-variation-settings by default.\n4. Correct the odd `em` font sizing in all browsers.\n*/\n\ncode,\nkbd,\nsamp,\npre {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace; /* 1 */\n font-feature-settings: normal; /* 2 */\n font-variation-settings: normal; /* 3 */\n font-size: 1em; /* 4 */\n}\n\n/*\nAdd the correct font size in all browsers.\n*/\n\nsmall {\n font-size: 80%;\n}\n\n/*\nPrevent `sub` and `sup` elements from affecting the line height in all browsers.\n*/\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/*\n1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)\n2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)\n3. Remove gaps between table borders by default.\n*/\n\ntable {\n text-indent: 0; /* 1 */\n border-color: inherit; /* 2 */\n border-collapse: collapse; /* 3 */\n}\n\n/*\n1. Change the font styles in all browsers.\n2. Remove the margin in Firefox and Safari.\n3. Remove default padding in all browsers.\n*/\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-feature-settings: inherit; /* 1 */\n font-variation-settings: inherit; /* 1 */\n font-size: 100%; /* 1 */\n font-weight: inherit; /* 1 */\n line-height: inherit; /* 1 */\n color: inherit; /* 1 */\n margin: 0; /* 2 */\n padding: 0; /* 3 */\n}\n\n/*\nRemove the inheritance of text transform in Edge and Firefox.\n*/\n\nbutton,\nselect {\n text-transform: none;\n}\n\n/*\n1. Correct the inability to style clickable types in iOS and Safari.\n2. Remove default button styles.\n*/\n\nbutton,\n[type='button'],\n[type='reset'],\n[type='submit'] {\n -webkit-appearance: button; /* 1 */\n background-color: transparent; /* 2 */\n background-image: none; /* 2 */\n}\n\n/*\nUse the modern Firefox focus style for all focusable elements.\n*/\n\n:-moz-focusring {\n outline: auto;\n}\n\n/*\nRemove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)\n*/\n\n:-moz-ui-invalid {\n box-shadow: none;\n}\n\n/*\nAdd the correct vertical alignment in Chrome and Firefox.\n*/\n\nprogress {\n vertical-align: baseline;\n}\n\n/*\nCorrect the cursor style of increment and decrement buttons in Safari.\n*/\n\n::-webkit-inner-spin-button,\n::-webkit-outer-spin-button {\n height: auto;\n}\n\n/*\n1. Correct the odd appearance in Chrome and Safari.\n2. Correct the outline style in Safari.\n*/\n\n[type='search'] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/*\nRemove the inner padding in Chrome and Safari on macOS.\n*/\n\n::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/*\n1. Correct the inability to style clickable types in iOS and Safari.\n2. Change font properties to `inherit` in Safari.\n*/\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/*\nAdd the correct display in Chrome and Safari.\n*/\n\nsummary {\n display: list-item;\n}\n\n/*\nRemoves the default spacing and border for appropriate elements.\n*/\n\nblockquote,\ndl,\ndd,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nhr,\nfigure,\np,\npre {\n margin: 0;\n}\n\nfieldset {\n margin: 0;\n padding: 0;\n}\n\nlegend {\n padding: 0;\n}\n\nol,\nul,\nmenu {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n/*\nReset default styling for dialogs.\n*/\ndialog {\n padding: 0;\n}\n\n/*\nPrevent resizing textareas horizontally by default.\n*/\n\ntextarea {\n resize: vertical;\n}\n\n/*\n1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)\n2. Set the default placeholder color to the user's configured gray 400 color.\n*/\n\ninput::-moz-placeholder, textarea::-moz-placeholder {\n opacity: 1; /* 1 */\n color: #9ca3af; /* 2 */\n}\n\ninput::placeholder,\ntextarea::placeholder {\n opacity: 1; /* 1 */\n color: #9ca3af; /* 2 */\n}\n\n/*\nSet the default cursor for buttons.\n*/\n\nbutton,\n[role=\"button\"] {\n cursor: pointer;\n}\n\n/*\nMake sure disabled buttons don't get the pointer cursor.\n*/\n:disabled {\n cursor: default;\n}\n\n/*\n1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)\n2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)\n This can trigger a poorly considered lint error in some tools but is included by design.\n*/\n\nimg,\nsvg,\nvideo,\ncanvas,\naudio,\niframe,\nembed,\nobject {\n display: block; /* 1 */\n vertical-align: middle; /* 2 */\n}\n\n/*\nConstrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)\n*/\n\nimg,\nvideo {\n max-width: 100%;\n height: auto;\n}\n\n/* Make elements with the HTML hidden attribute stay hidden by default */\n[hidden] {\n display: none;\n}\n\n*, ::before, ::after {\n --tw-border-spacing-x: 0;\n --tw-border-spacing-y: 0;\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-rotate: 0;\n --tw-skew-x: 0;\n --tw-skew-y: 0;\n --tw-scale-x: 1;\n --tw-scale-y: 1;\n --tw-pan-x: ;\n --tw-pan-y: ;\n --tw-pinch-zoom: ;\n --tw-scroll-snap-strictness: proximity;\n --tw-gradient-from-position: ;\n --tw-gradient-via-position: ;\n --tw-gradient-to-position: ;\n --tw-ordinal: ;\n --tw-slashed-zero: ;\n --tw-numeric-figure: ;\n --tw-numeric-spacing: ;\n --tw-numeric-fraction: ;\n --tw-ring-inset: ;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-color: rgb(59 130 246 / 0.5);\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-ring-shadow: 0 0 #0000;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-colored: 0 0 #0000;\n --tw-blur: ;\n --tw-brightness: ;\n --tw-contrast: ;\n --tw-grayscale: ;\n --tw-hue-rotate: ;\n --tw-invert: ;\n --tw-saturate: ;\n --tw-sepia: ;\n --tw-drop-shadow: ;\n --tw-backdrop-blur: ;\n --tw-backdrop-brightness: ;\n --tw-backdrop-contrast: ;\n --tw-backdrop-grayscale: ;\n --tw-backdrop-hue-rotate: ;\n --tw-backdrop-invert: ;\n --tw-backdrop-opacity: ;\n --tw-backdrop-saturate: ;\n --tw-backdrop-sepia: ;\n}\n\n::backdrop {\n --tw-border-spacing-x: 0;\n --tw-border-spacing-y: 0;\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-rotate: 0;\n --tw-skew-x: 0;\n --tw-skew-y: 0;\n --tw-scale-x: 1;\n --tw-scale-y: 1;\n --tw-pan-x: ;\n --tw-pan-y: ;\n --tw-pinch-zoom: ;\n --tw-scroll-snap-strictness: proximity;\n --tw-gradient-from-position: ;\n --tw-gradient-via-position: ;\n --tw-gradient-to-position: ;\n --tw-ordinal: ;\n --tw-slashed-zero: ;\n --tw-numeric-figure: ;\n --tw-numeric-spacing: ;\n --tw-numeric-fraction: ;\n --tw-ring-inset: ;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-color: rgb(59 130 246 / 0.5);\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-ring-shadow: 0 0 #0000;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-colored: 0 0 #0000;\n --tw-blur: ;\n --tw-brightness: ;\n --tw-contrast: ;\n --tw-grayscale: ;\n --tw-hue-rotate: ;\n --tw-invert: ;\n --tw-saturate: ;\n --tw-sepia: ;\n --tw-drop-shadow: ;\n --tw-backdrop-blur: ;\n --tw-backdrop-brightness: ;\n --tw-backdrop-contrast: ;\n --tw-backdrop-grayscale: ;\n --tw-backdrop-hue-rotate: ;\n --tw-backdrop-invert: ;\n --tw-backdrop-opacity: ;\n --tw-backdrop-saturate: ;\n --tw-backdrop-sepia: ;\n}\n.container {\n width: 100%;\n}\n@media (min-width: 640px) {\n\n .container {\n max-width: 640px;\n }\n}\n@media (min-width: 768px) {\n\n .container {\n max-width: 768px;\n }\n}\n@media (min-width: 1024px) {\n\n .container {\n max-width: 1024px;\n }\n}\n@media (min-width: 1280px) {\n\n .container {\n max-width: 1280px;\n }\n}\n@media (min-width: 1536px) {\n\n .container {\n max-width: 1536px;\n }\n}\n.pointer-events-none {\n pointer-events: none;\n}\n.static {\n position: static;\n}\n.fixed {\n position: fixed;\n}\n.absolute {\n position: absolute;\n}\n.relative {\n position: relative;\n}\n.inset-0 {\n inset: 0px;\n}\n.top-0 {\n top: 0px;\n}\n.z-10 {\n z-index: 10;\n}\n.z-20 {\n z-index: 20;\n}\n.col-span-2 {\n grid-column: span 2 / span 2;\n}\n.mb-\\[1px\\] {\n margin-bottom: 1px;\n}\n.block {\n display: block;\n}\n.inline-block {\n display: inline-block;\n}\n.inline {\n display: inline;\n}\n.flex {\n display: flex;\n}\n.grid {\n display: grid;\n}\n.contents {\n display: contents;\n}\n.hidden {\n display: none;\n}\n.h-\\[1\\.25rem\\] {\n height: 1.25rem;\n}\n.h-\\[5px\\] {\n height: 5px;\n}\n.h-full {\n height: 100%;\n}\n.w-1 {\n width: 0.25rem;\n}\n.w-\\[2px\\] {\n width: 2px;\n}\n.w-full {\n width: 100%;\n}\n.transform {\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.cursor-crosshair {\n cursor: crosshair;\n}\n.place-content-center {\n place-content: center;\n}\n.place-items-center {\n place-items: center;\n}\n.items-center {\n align-items: center;\n}\n.overflow-auto {\n overflow: auto;\n}\n.overflow-hidden {\n overflow: hidden;\n}\n.text-nowrap {\n text-wrap: nowrap;\n}\n.border {\n border-width: 1px;\n}\n.border-r-2 {\n border-right-width: 2px;\n}\n.border-blue-500 {\n --tw-border-opacity: 1;\n border-color: rgb(59 130 246 / var(--tw-border-opacity));\n}\n.border-red-700 {\n --tw-border-opacity: 1;\n border-color: rgb(185 28 28 / var(--tw-border-opacity));\n}\n.border-slate-500 {\n --tw-border-opacity: 1;\n border-color: rgb(100 116 139 / var(--tw-border-opacity));\n}\n.border-b-slate-600 {\n --tw-border-opacity: 1;\n border-bottom-color: rgb(71 85 105 / var(--tw-border-opacity));\n}\n.bg-blue-200 {\n --tw-bg-opacity: 1;\n background-color: rgb(191 219 254 / var(--tw-bg-opacity));\n}\n.bg-blue-500 {\n --tw-bg-opacity: 1;\n background-color: rgb(59 130 246 / var(--tw-bg-opacity));\n}\n.bg-red-500 {\n --tw-bg-opacity: 1;\n background-color: rgb(239 68 68 / var(--tw-bg-opacity));\n}\n.bg-slate-100 {\n --tw-bg-opacity: 1;\n background-color: rgb(241 245 249 / var(--tw-bg-opacity));\n}\n.bg-slate-200 {\n --tw-bg-opacity: 1;\n background-color: rgb(226 232 240 / var(--tw-bg-opacity));\n}\n.bg-opacity-20 {\n --tw-bg-opacity: 0.2;\n}\n.object-fill {\n -o-object-fit: fill;\n object-fit: fill;\n}\n.p-\\[1px\\] {\n padding: 1px;\n}\n.pb-0 {\n padding-bottom: 0px;\n}\n.pl-1 {\n padding-left: 0.25rem;\n}\n.pl-2 {\n padding-left: 0.5rem;\n}\n.pr-0 {\n padding-right: 0px;\n}\n.pr-1 {\n padding-right: 0.25rem;\n}\n.pt-2 {\n padding-top: 0.5rem;\n}\n.text-sm {\n font-size: 0.875rem;\n line-height: 1.25rem;\n}\n.opacity-50 {\n opacity: 0.5;\n}\n.shadow {\n --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);\n box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n}\n.shadow-slate-300 {\n --tw-shadow-color: #cbd5e1;\n --tw-shadow: var(--tw-shadow-colored);\n}\n.shadow-slate-600 {\n --tw-shadow-color: #475569;\n --tw-shadow: var(--tw-shadow-colored);\n}\n.filter {\n filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n}\n.transition {\n transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;\n transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;\n transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n}\n.hover\\:bg-slate-400:hover {\n --tw-bg-opacity: 1;\n background-color: rgb(148 163 184 / var(--tw-bg-opacity));\n}\n.peer:hover ~ .peer-hover\\:border-slate-400 {\n --tw-border-opacity: 1;\n border-color: rgb(148 163 184 / var(--tw-border-opacity));\n}\n.peer:hover ~ .peer-hover\\:bg-slate-300 {\n --tw-bg-opacity: 1;\n background-color: rgb(203 213 225 / var(--tw-bg-opacity));\n}\n.data-\\[focused\\]\\:bg-slate-400[data-focused] {\n --tw-bg-opacity: 1;\n background-color: rgb(148 163 184 / var(--tw-bg-opacity));\n}\n.peer[data-focused] ~ .peer-data-\\[focused\\]\\:border-slate-400 {\n --tw-border-opacity: 1;\n border-color: rgb(148 163 184 / var(--tw-border-opacity));\n}\n.peer[data-focused] ~ .peer-data-\\[focused\\]\\:bg-slate-300 {\n --tw-bg-opacity: 1;\n background-color: rgb(203 213 225 / var(--tw-bg-opacity));\n}\n";
2
- export {
3
- twStyle as default
4
- };
@@ -1,11 +0,0 @@
1
- const awaitAnimationFrame = () => {
2
- return new Promise((resolve) => {
3
- requestAnimationFrame((timestamp) => {
4
- console.log("AnimationFrame", timestamp);
5
- resolve(timestamp);
6
- });
7
- });
8
- };
9
- export {
10
- awaitAnimationFrame
11
- };
@@ -1,17 +0,0 @@
1
-
2
- services:
3
- build-elements:
4
- image: telecine-runner
5
- depends_on:
6
- - runner
7
- working_dir: /app
8
- env_file:
9
- - ${PWD}/.env
10
- volumes_from:
11
- - runner
12
- stop_grace_period: 1s
13
- init: true
14
- scale: 1
15
- environment:
16
- - SHELL=/bin/sh
17
- command: npx vite build --config lib/elements/vite.config.ts lib/elements/src/elements.ts --watch
@@ -1,208 +0,0 @@
1
- import type { VideoRenderOptions } from "services/render/app/RenderOptions";
2
- import {
3
- deepGetElementsWithFrameTasks,
4
- shallowGetTimegroups,
5
- } from "./elements/EFTemporal";
6
- import { awaitAnimationFrame } from "@/util/awaitAnimationFrame";
7
- import { awaitMicrotask } from "@/util/awaitMicrotask";
8
-
9
- declare global {
10
- interface Window {
11
- EF_FRAMEGEN?: {
12
- onRender: (
13
- callback: (
14
- renderId: string,
15
- traceCarrier: unknown,
16
- renderOptions: VideoRenderOptions,
17
- ) => void,
18
- ) => void;
19
- onBegin: (
20
- callback: (
21
- traceCarrier: unknown,
22
- frame: number,
23
- isLast: boolean,
24
- ) => void,
25
- ) => void;
26
- frameReady: (
27
- renderId: string,
28
- frameNumber: number,
29
- samples: ArrayBufferLike,
30
- ) => void;
31
- onPaint: (callback: () => void) => void;
32
- didPaint: (renderId: string) => void;
33
- end: (renderId: string) => void;
34
- error: (renderId: string, error: string) => void;
35
- };
36
- }
37
- }
38
-
39
- class TriggerCanvas {
40
- private canvas: HTMLCanvasElement;
41
- private ctx: CanvasRenderingContext2D;
42
-
43
- constructor() {
44
- this.canvas = document.createElement("canvas");
45
- this.canvas.width = 1;
46
- this.canvas.height = 1;
47
- Object.assign(this.canvas.style, {
48
- position: "absolute",
49
- top: "0px",
50
- left: "0px",
51
- width: `1px`,
52
- height: `1px`,
53
- zIndex: "100000",
54
- });
55
- document.body.prepend(this.canvas);
56
- this.ctx = this.canvas.getContext("2d")!;
57
- }
58
-
59
- trigger() {
60
- console.log("Triggering");
61
- this.ctx.fillStyle = "rgba(50, 0, 0, .8)";
62
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
63
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
64
- }
65
- }
66
-
67
- if (window.EF_FRAMEGEN !== undefined) {
68
- const EF_FRAMEGEN = window.EF_FRAMEGEN;
69
- EF_FRAMEGEN.onRender((renderId, traceCarrier, renderOptions) => {
70
- const crashOnUnhandledError = (error: string) => {
71
- EF_FRAMEGEN.error(renderId, error);
72
- };
73
- window.addEventListener("error", (error) => {
74
- console.warn("Crashing due to unhandled error", error);
75
- crashOnUnhandledError(error.message);
76
- });
77
- window.addEventListener("unhandledrejection", (error) => {
78
- console.warn("Crashing due to unhandled rejection", error);
79
- crashOnUnhandledError(error.reason);
80
- });
81
-
82
- const triggerCanvas = new TriggerCanvas();
83
- EF_FRAMEGEN.onPaint(async () => {
84
- // Calls clearRect on the canvas to force the compositor to emit a frame.
85
- triggerCanvas.trigger();
86
-
87
- // We must wait for 2 frames to ensure offscreen render capture doesn't capture
88
- // a stale frame. This is terrible. Fixing this requires contribiting to electron.js
89
- // On the positive side, were' running at 240fps so it only burns 8ms max.
90
- // (electron can't go faster thon 240fps)
91
- // In practice it's less because we're already some ms into the next frame budget.
92
- await awaitAnimationFrame();
93
- await awaitAnimationFrame();
94
-
95
- EF_FRAMEGEN.didPaint(renderId);
96
- });
97
- const workbench = document.querySelector("ef-workbench")!;
98
- workbench.rendering = true;
99
- const timegroups = shallowGetTimegroups(workbench);
100
- const temporals = deepGetElementsWithFrameTasks(workbench);
101
- const firstGroup = timegroups[0];
102
- if (!firstGroup) {
103
- throw new Error("No temporal elements found");
104
- }
105
- firstGroup.currentTimeMs = renderOptions.encoderOptions.fromMs;
106
-
107
- const frameDurationMs = 1000 / renderOptions.encoderOptions.video.framerate;
108
-
109
- const initialBusyTasks = Promise.all(
110
- temporals
111
- // .filter((temporal) => temporal.frameTask.status < TaskStatus.COMPLETE)
112
- .map((temporal) => temporal.frameTask)
113
- .map((task) => task.taskComplete),
114
- );
115
-
116
- const frameBox = document.createElement("div");
117
- Object.assign(frameBox.style, {
118
- width: "200px",
119
- height: "100px",
120
- font: "30px Arial",
121
- backgroundColor: "white",
122
- position: "absolute",
123
- top: "0px",
124
- left: "0px",
125
- zIndex: "100000",
126
- });
127
-
128
- let time = 0;
129
- // document.body.prepend(frameBox);
130
- // TODO: terminate if no workbench found
131
- console.log("onRender", renderId, traceCarrier, renderOptions);
132
-
133
- const audioBufferPromise = firstGroup.renderAudio(
134
- renderOptions.encoderOptions.alignedFromUs / 1000,
135
- renderOptions.encoderOptions.alignedToUs / 1000,
136
- // renderOptions.encoderOptions.fromMs,
137
- // renderOptions.encoderOptions.toMs,
138
- );
139
- EF_FRAMEGEN.onBegin(async (traceCarrier, frame, isLast) => {
140
- time = firstGroup.currentTimeMs =
141
- renderOptions.encoderOptions.fromMs + frame * frameDurationMs;
142
- console.log("FRAME #", frame);
143
- frameBox.innerHTML = `
144
- <div>Frame #${frame}</div>
145
- <div>${time.toFixed(4)}</div>
146
- `;
147
-
148
- await initialBusyTasks;
149
-
150
- console.log("TIME", time.toFixed(4));
151
- await awaitMicrotask();
152
- console.log("After microtask");
153
-
154
- const now = performance.now();
155
- console.log(`frame:${frame} Awaiting busyTasks`);
156
- await Promise.all(
157
- temporals
158
- // .filter((temporal) => temporal.frameTask.status < TaskStatus.COMPLETE)
159
- .map((temporal) => {
160
- console.log(
161
- "Awaiting",
162
- temporal.tagName,
163
- temporal.frameTask.status,
164
- temporal.frameTask.taskComplete,
165
- );
166
- return temporal.frameTask;
167
- })
168
- .map((task) => task.taskComplete),
169
- );
170
- console.log(
171
- `frame:${frame} All tasks complete ${performance.now() - now}ms`,
172
- );
173
-
174
- await awaitAnimationFrame();
175
- await awaitAnimationFrame();
176
-
177
- // Trigger a small canvas paint to force the compositor to emit a frame.
178
- triggerCanvas.trigger();
179
-
180
- if (isLast) {
181
- // Currently we emit the audio in one belch at the end of the render.
182
- // This is not ideal, but it's the simplest thing that could possibly work.
183
- // We could either emit it slices, or in parallel with the video.
184
- // But in any case, it's fine for now.
185
- const renderedAudio = await audioBufferPromise;
186
-
187
- const channelCount = renderedAudio.numberOfChannels;
188
-
189
- const interleavedSamples = new Float32Array(
190
- channelCount * renderedAudio.length,
191
- );
192
-
193
- for (let i = 0; i < renderedAudio.length; i++) {
194
- for (let j = 0; j < channelCount; j++) {
195
- interleavedSamples.set(
196
- renderedAudio.getChannelData(j).slice(i, i + 1),
197
- i * channelCount + j,
198
- );
199
- }
200
- }
201
-
202
- EF_FRAMEGEN.frameReady(renderId, frame, interleavedSamples.buffer);
203
- } else {
204
- EF_FRAMEGEN.frameReady(renderId, frame, new Float32Array(0).buffer);
205
- }
206
- });
207
- });
208
- }
@@ -1,2 +0,0 @@
1
- export const EF_INTERACTIVE =
2
- !window.location.search.includes("EF_NONINTERACTIVE");
@@ -1,18 +0,0 @@
1
- import { LitElement, ReactiveController, ReactiveControllerHost } from "lit";
2
-
3
- export class CrossUpdateController implements ReactiveController {
4
- constructor(
5
- private host: ReactiveControllerHost,
6
- private target: LitElement,
7
- ) {
8
- this.host.addController(this);
9
- }
10
-
11
- hostUpdate(): void {
12
- this.target.requestUpdate();
13
- }
14
-
15
- remove(): void {
16
- this.host.removeController(this);
17
- }
18
- }
@@ -1,42 +0,0 @@
1
- import { html } from "lit";
2
- import { createRef, ref } from "lit/directives/ref.js";
3
- import { customElement, property } from "lit/decorators.js";
4
- import { EFMedia } from "./EFMedia";
5
- import { Task } from "@lit/task";
6
-
7
- @customElement("ef-audio")
8
- export class EFAudio extends EFMedia {
9
- audioElementRef = createRef<HTMLAudioElement>();
10
-
11
- @property({ type: String })
12
- src = "";
13
-
14
- render() {
15
- return html`<audio ${ref(this.audioElementRef)}></audio>`;
16
- }
17
-
18
- get audioElement() {
19
- return this.audioElementRef.value;
20
- }
21
-
22
- frameTask = new Task(this, {
23
- args: () =>
24
- [
25
- this.trackFragmentIndexLoader.status,
26
- this.initSegmentsLoader.status,
27
- this.seekTask.status,
28
- this.fetchSeekTask.status,
29
- this.videoAssetTask.status,
30
- ] as const,
31
- task: async () => {
32
- console.log("EFAudio frameTask", this.ownCurrentTimeMs);
33
- await this.trackFragmentIndexLoader.taskComplete;
34
- await this.initSegmentsLoader.taskComplete;
35
- await this.seekTask.taskComplete;
36
- await this.fetchSeekTask.taskComplete;
37
- await this.videoAssetTask.taskComplete;
38
- console.log("EFAudio frameTask complete", this.ownCurrentTimeMs);
39
- this.rootTimegroup?.requestUpdate();
40
- },
41
- });
42
- }
@@ -1,202 +0,0 @@
1
- import { EFAudio } from "./EFAudio";
2
- import { LitElement, PropertyValueMap, html, css } from "lit";
3
- import { Task } from "@lit/task";
4
- import { customElement, property } from "lit/decorators.js";
5
- import { EFVideo } from "./EFVideo";
6
- import { EFTemporal } from "./EFTemporal";
7
- import { CrossUpdateController } from "./CrossUpdateController";
8
- import { FetchMixin } from "./FetchMixin";
9
- import { EFSourceMixin } from "./EFSourceMixin";
10
- import { EF_INTERACTIVE } from "../EF_INTERACTIVE";
11
-
12
- interface Word {
13
- text: string;
14
- start: number;
15
- end: number;
16
- confidence: number;
17
- }
18
-
19
- interface Segment {
20
- start: number;
21
- end: number;
22
- text: string;
23
- confidence: number;
24
- words: Word[];
25
- }
26
-
27
- interface Caption {
28
- text: string;
29
- segments: Segment[];
30
- language: string;
31
- }
32
-
33
- @customElement("ef-captions-active-word")
34
- export class EFCaptionsActiveWord extends EFTemporal(LitElement) {
35
- static styles = [
36
- css`
37
- :host {
38
- display: inline-block;
39
- }
40
- `,
41
- ];
42
-
43
- render() {
44
- return html`${this.wordText}`;
45
- }
46
-
47
- @property({ type: Number, attribute: false })
48
- wordStartMs = 0;
49
-
50
- @property({ type: Number, attribute: false })
51
- wordEndMs = 0;
52
-
53
- @property({ type: String, attribute: false })
54
- wordText = "";
55
-
56
- get startTimeMs() {
57
- return this.wordStartMs || 0;
58
- }
59
-
60
- get durationMs(): number {
61
- return this.wordEndMs - this.wordStartMs;
62
- }
63
- }
64
-
65
- @customElement("ef-captions")
66
- export class EFCaptions extends EFSourceMixin(
67
- EFTemporal(FetchMixin(LitElement)),
68
- { assetType: "caption_files" },
69
- ) {
70
- static styles = [
71
- css`
72
- :host {
73
- display: block;
74
- }
75
- `,
76
- ];
77
-
78
- @property({ type: String, attribute: "target" })
79
- target = null;
80
-
81
- @property({ attribute: "word-style" })
82
- wordStyle = "";
83
-
84
- activeWordContainers = this.getElementsByTagName("ef-captions-active-word");
85
-
86
- captionsPath() {
87
- const src = this.targetElement.getAttribute("src");
88
- if (src?.startsWith("http")) {
89
- return src.replace("isobmff", "caption");
90
- }
91
- return `/@ef-captions/${this.targetElement.getAttribute("src") ?? ""}`;
92
- }
93
-
94
- protected md5SumLoader = new Task(this, {
95
- autoRun: false,
96
- args: () => [this.target] as const,
97
- task: async ([], { signal }) => {
98
- const md5Path = `/@ef-asset/${this.targetElement.getAttribute("src") ?? ""}`;
99
- const response = await fetch(md5Path, { method: "HEAD", signal });
100
- return response.headers.get("etag") ?? undefined;
101
- },
102
- });
103
-
104
- productionSrc() {
105
- if (!this.md5SumLoader.value) {
106
- throw new Error(
107
- `MD5 sum not available for ${this}. Cannot generate production URL`,
108
- );
109
- }
110
- return `http://localhost:3000/api/video2/caption_files/${this.md5SumLoader.value}`;
111
- }
112
-
113
- // get requiredAssets() {
114
- // return { [this.md5SumLoader.value]: [this.captionsPath()] };
115
- // }
116
-
117
- private captionsDataTask = new Task(this, {
118
- autoRun: EF_INTERACTIVE,
119
- args: () => [this.captionsPath(), this.fetch] as const,
120
- task: async ([captionsPath, fetch], { signal }) => {
121
- const response = await fetch(captionsPath, { signal });
122
- return response.json() as any as Caption;
123
- },
124
- });
125
-
126
- frameTask = new Task(this, {
127
- autoRun: EF_INTERACTIVE,
128
- args: () => [this.captionsDataTask.status],
129
- task: async () => {
130
- await this.captionsDataTask.taskComplete;
131
- },
132
- });
133
-
134
- connectedCallback() {
135
- super.connectedCallback();
136
- if (this.targetElement) {
137
- new CrossUpdateController(this.targetElement, this);
138
- }
139
- }
140
-
141
- render() {
142
- return this.captionsDataTask.render({
143
- pending: () => html`<div>Generating captions data...</div>`,
144
- error: () => html`<div>🚫 Error generating captions data</div>`,
145
- complete: () => html`<slot></slot>`,
146
- });
147
- }
148
-
149
- protected updated(
150
- _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
151
- ): void {
152
- this.updateActiveWord();
153
- }
154
-
155
- updateActiveWord() {
156
- const caption = this.captionsDataTask.value;
157
- if (!caption) {
158
- return;
159
- }
160
- const words: string[] = [];
161
- let startMs: number;
162
- let endMs: number;
163
- caption.segments.forEach((segment) => {
164
- if (
165
- this.targetElement.ownCurrentTimeMs >= segment.start * 1000 &&
166
- this.targetElement.ownCurrentTimeMs <= segment.end * 1000
167
- ) {
168
- return segment.words.map((word) => {
169
- if (
170
- this.targetElement.ownCurrentTimeMs >= word.start * 1000 &&
171
- this.targetElement.ownCurrentTimeMs <= word.end * 1000
172
- ) {
173
- words.push(word.text);
174
- startMs = word.start * 1000;
175
- endMs = word.end * 1000;
176
- } else {
177
- }
178
- });
179
- }
180
- });
181
- Array.from(this.activeWordContainers).forEach((container) => {
182
- container.wordText = words.join(" ");
183
- container.wordStartMs = startMs;
184
- container.wordEndMs = endMs;
185
- });
186
- }
187
-
188
- get targetElement() {
189
- const target = document.querySelector(this.getAttribute("target") ?? "");
190
- if (target instanceof EFAudio || target instanceof EFVideo) {
191
- return target;
192
- }
193
- throw new Error("Invalid target, must be an EFAudio or EFVideo element");
194
- }
195
- }
196
-
197
- declare global {
198
- interface HTMLElementTagNameMap {
199
- "ef-captions": EFCaptions;
200
- "ef-captions-active-word": EFCaptionsActiveWord;
201
- }
202
- }