@bendyline/squisq-video-react 2.1.0 → 2.2.1

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/NOTICE.md CHANGED
@@ -4,38 +4,30 @@ This notice applies to the `@bendyline/squisq-video-react` npm package.
4
4
  Squisq-authored code is licensed under the MIT license in `LICENSE`.
5
5
  Third-party components remain under their respective license terms.
6
6
 
7
- ## Runtime and peer dependencies
8
-
9
- | Package | Version | License | Repository |
10
- | ----------------------- | --------- | ---------------- | ----------------------------------------- |
11
- | @bendyline/squisq | 2.1.0 | MIT | https://github.com/bendyline/squisq |
12
- | @bendyline/squisq-react | 2.1.0 | MIT | https://github.com/bendyline/squisq |
13
- | @bendyline/squisq-video | 2.0.2 | MIT | https://github.com/bendyline/squisq |
14
- | @ffmpeg/core | 0.12.9 | GPL-2.0-or-later | https://github.com/ffmpegwasm/ffmpeg.wasm |
15
- | @ffmpeg/ffmpeg | 0.12.15 | MIT | https://github.com/ffmpegwasm/ffmpeg.wasm |
16
- | @ffmpeg/util | 0.12.2 | MIT | https://github.com/ffmpegwasm/ffmpeg.wasm |
17
- | html2canvas | 1.4.1 | MIT | https://github.com/niklasvh/html2canvas |
18
- | mp4-muxer | 5.2.2 | MIT | https://github.com/Vanilagy/mp4-muxer |
19
- | react _(peer)_ | ^18 / ^19 | MIT | https://github.com/facebook/react |
20
- | react-dom _(peer)_ | ^18 / ^19 | MIT | https://github.com/facebook/react |
7
+ ## Runtime, peer, and bundled dependencies
8
+
9
+ | Package | Version | License | Repository |
10
+ | --------------------- | -------------------- | ---------------- | ----------------------------------------- |
11
+ | @ffmpeg/core | 0.12.9 | GPL-2.0-or-later | https://github.com/ffmpegwasm/ffmpeg.wasm |
12
+ | @ffmpeg/ffmpeg | 0.12.15 | MIT | https://github.com/ffmpegwasm/ffmpeg.wasm |
13
+ | @ffmpeg/util | 0.12.2 | MIT | https://github.com/ffmpegwasm/ffmpeg.wasm |
14
+ | html2canvas | 1.4.1 | MIT | https://github.com/niklasvh/html2canvas |
15
+ | mp4-muxer _(bundled)_ | 5.2.2 | MIT | https://github.com/Vanilagy/mp4-muxer |
16
+ | react _(peer)_ | ^18.0.0 \|\| ^19.0.0 | MIT | https://github.com/facebook/react |
17
+ | react-dom _(peer)_ | ^18.0.0 \|\| ^19.0.0 | MIT | https://github.com/facebook/react |
21
18
 
22
19
  ## @ffmpeg/core WebAssembly runtime
23
20
 
24
- `@ffmpeg/core` is a WebAssembly build with an upstream dependency on the
25
- FFmpeg project and external libraries. The license applicable to a particular
26
- core build follows the components and configuration used to produce it. The
27
- `@ffmpeg/core@0.12.9` package metadata declares `GPL-2.0-or-later`.
28
-
29
- The core runtime is distributed separately from this package's MIT-licensed
30
- Squisq code. A verbatim copy of GPLv2 is included as
31
- `COPYING.GPL-2.0.txt`. Hosts that publish `ffmpeg-core.js` or
32
- `ffmpeg-core.wasm` must preserve the applicable notices, provide the GPL text,
33
- and provide equivalent access to the complete corresponding source for the
34
- exact binaries.
35
-
36
- Squisq's demo site uses the unmodified ESM files from `@ffmpeg/core@0.12.9`.
37
- Upstream identifies ffmpeg.wasm release `v12.14`, commit
38
- `d3c018aa40a241384965268f0506b73f47dee60c`, as the source release containing
21
+ @ffmpeg/core is a WebAssembly build with an upstream dependency on the FFmpeg
22
+ project and external libraries. Version 0.12.9 declares GPL-2.0-or-later. A
23
+ verbatim copy of GPLv2 is included as COPYING.GPL-2.0.txt. Hosts that publish
24
+ ffmpeg-core.js or ffmpeg-core.wasm must preserve the applicable notices,
25
+ provide the GPL text, and provide equivalent access to the corresponding
26
+ source for the exact binaries.
27
+
28
+ Squisq's demo site uses the unmodified ESM files from @ffmpeg/core@0.12.9.
29
+ Upstream identifies ffmpeg.wasm release v12.14, commit
30
+ d3c018aa40a241384965268f0506b73f47dee60c, as the source release containing
39
31
  that package version:
40
32
 
41
33
  - https://github.com/ffmpegwasm/ffmpeg.wasm/releases/tag/v12.14
@@ -43,7 +35,6 @@ that package version:
43
35
  - https://github.com/ffmpegwasm/ffmpeg.wasm/archive/refs/tags/v12.14.tar.gz
44
36
  - https://ffmpegwasm.netlify.app/docs/contribution/core/
45
37
 
46
- The `@ffmpeg/ffmpeg` JavaScript wrapper and `@ffmpeg/util` package are separate
47
- MIT-licensed components. Copyright and complete license texts for other
48
- dependencies are included in their respective distributions and source
49
- repositories.
38
+ mp4-muxer is bundled as a private runtime implementation detail so its legacy
39
+ global WebCodecs declaration dependencies are not installed for consumers. Its
40
+ exact license text is shipped in THIRD_PARTY_LICENSES.txt.
@@ -0,0 +1,60 @@
1
+ THIRD-PARTY LICENSES FOR @bendyline/squisq-video-react
2
+
3
+ Generated from the actual esbuild input graph. Package-local license, copying,
4
+ and notice files are reproduced verbatim. When an npm tarball omits its
5
+ repository license, the pinned upstream copy is vendored and identified below.
6
+
7
+ COMPONENTS
8
+ - @ffmpeg/ffmpeg@0.12.15 (MIT); files: VENDORED-ffmpeg-wasm-0.12.15.txt
9
+ - mp4-muxer@5.2.2 (MIT); files: LICENSE
10
+
11
+ ==============================================================================
12
+ @ffmpeg/ffmpeg@0.12.15 - VENDORED-ffmpeg-wasm-0.12.15.txt
13
+ ==============================================================================
14
+ MIT License
15
+
16
+ Copyright (c) 2019 Jerome Wu
17
+
18
+ Permission is hereby granted, free of charge, to any person obtaining a copy
19
+ of this software and associated documentation files (the "Software"), to deal
20
+ in the Software without restriction, including without limitation the rights
21
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22
+ copies of the Software, and to permit persons to whom the Software is
23
+ furnished to do so, subject to the following conditions:
24
+
25
+ The above copyright notice and this permission notice shall be included in all
26
+ copies or substantial portions of the Software.
27
+
28
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34
+ SOFTWARE.
35
+
36
+ ==============================================================================
37
+ mp4-muxer@5.2.2 - LICENSE
38
+ ==============================================================================
39
+ MIT License
40
+
41
+ Copyright (c) 2023 Vanilagy
42
+
43
+ Permission is hereby granted, free of charge, to any person obtaining a copy
44
+ of this software and associated documentation files (the "Software"), to deal
45
+ in the Software without restriction, including without limitation the rights
46
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
47
+ copies of the Software, and to permit persons to whom the Software is
48
+ furnished to do so, subject to the following conditions:
49
+
50
+ The above copyright notice and this permission notice shall be included in all
51
+ copies or substantial portions of the Software.
52
+
53
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
54
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
55
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
56
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
57
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
58
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
59
+ SOFTWARE.
60
+
@@ -0,0 +1,549 @@
1
+ import {
2
+ useVideoExport
3
+ } from "./chunk-VILZP3GL.js";
4
+
5
+ // src/VideoExportModal.tsx
6
+ import { useState, useCallback, useId, useRef } from "react";
7
+ import { useModalDialog } from "@bendyline/squisq-react";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ function formatDuration(seconds) {
10
+ if (seconds < 60) return `${seconds}s`;
11
+ const m = Math.floor(seconds / 60);
12
+ const s = seconds % 60;
13
+ return `${m}m ${s}s`;
14
+ }
15
+ function encoderLabel(outputFormat, backend) {
16
+ if (outputFormat === "gif") {
17
+ return backend === "webcodecs" ? "WebCodecs (H.264) \u2192 ffmpeg.wasm (GIF)" : "ffmpeg.wasm (H.264 \u2192 GIF)";
18
+ }
19
+ return backend === "webcodecs" ? "WebCodecs (H.264)" : "ffmpeg.wasm (H.264)";
20
+ }
21
+ var VIDEO_EXPORT_PALETTES = {
22
+ light: {
23
+ overlay: "rgba(0, 0, 0, 0.5)",
24
+ surface: "#FFFDF7",
25
+ control: "#ffffff",
26
+ border: "#c9b98a",
27
+ text: "#4a3c1f",
28
+ heading: "#2d2310",
29
+ label: "#5a4a2a",
30
+ muted: "#8a7a5a",
31
+ secondary: "#E8DFC6",
32
+ primary: "#8B6914",
33
+ primaryBorder: "#7a5c10",
34
+ primaryText: "#ffffff",
35
+ success: "#2d6a10",
36
+ danger: "#a03020"
37
+ },
38
+ dark: {
39
+ overlay: "rgba(2, 6, 23, 0.72)",
40
+ surface: "#111827",
41
+ control: "#0f172a",
42
+ border: "#475569",
43
+ text: "#e5e7eb",
44
+ heading: "#f8fafc",
45
+ label: "#cbd5e1",
46
+ muted: "#94a3b8",
47
+ secondary: "#1e293b",
48
+ primary: "#9a7416",
49
+ primaryBorder: "#d1a73b",
50
+ primaryText: "#ffffff",
51
+ success: "#86efac",
52
+ danger: "#fca5a5"
53
+ }
54
+ };
55
+ var overlayStyle = {
56
+ position: "fixed",
57
+ inset: 0,
58
+ display: "flex",
59
+ alignItems: "center",
60
+ justifyContent: "center",
61
+ zIndex: 1e4
62
+ };
63
+ var modalStyle = {
64
+ borderRadius: 0,
65
+ padding: "24px 28px",
66
+ minWidth: 380,
67
+ maxWidth: 480,
68
+ boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
69
+ fontFamily: "system-ui, -apple-system, sans-serif"
70
+ };
71
+ var titleStyle = {
72
+ margin: "0 0 16px 0",
73
+ fontSize: 18,
74
+ fontWeight: 600
75
+ };
76
+ var labelStyle = {
77
+ display: "block",
78
+ fontSize: 13,
79
+ fontWeight: 500,
80
+ marginBottom: 4
81
+ };
82
+ var selectStyle = {
83
+ width: "100%",
84
+ padding: "6px 8px",
85
+ fontSize: 13,
86
+ fontFamily: "inherit",
87
+ borderRadius: 0,
88
+ marginBottom: 12
89
+ };
90
+ var btnPrimary = {
91
+ padding: "8px 20px",
92
+ fontSize: 14,
93
+ fontFamily: "inherit",
94
+ fontWeight: 500,
95
+ cursor: "pointer",
96
+ borderRadius: 0
97
+ };
98
+ var btnSecondary = {
99
+ padding: "8px 20px",
100
+ fontSize: 14,
101
+ fontFamily: "inherit",
102
+ fontWeight: 500,
103
+ cursor: "pointer",
104
+ borderRadius: 0
105
+ };
106
+ var progressBarOuterStyle = {
107
+ width: "100%",
108
+ height: 8,
109
+ borderRadius: 0,
110
+ overflow: "hidden",
111
+ marginBottom: 8
112
+ };
113
+ var footerStyle = {
114
+ display: "flex",
115
+ justifyContent: "flex-end",
116
+ gap: 8,
117
+ marginTop: 20
118
+ };
119
+ function VideoExportModal({
120
+ doc,
121
+ playerScript,
122
+ mediaProvider,
123
+ images,
124
+ audio,
125
+ defaultConfig,
126
+ colorScheme = "light",
127
+ uiPalette,
128
+ onClose
129
+ }) {
130
+ const overlayRef = useRef(null);
131
+ const dialogRef = useRef(null);
132
+ const titleId = useId();
133
+ const initialOutputFormat = defaultConfig?.outputFormat ?? "mp4";
134
+ const [outputFormat, setOutputFormat] = useState(initialOutputFormat);
135
+ const [quality, setQuality] = useState(defaultConfig?.quality ?? "normal");
136
+ const [fps, setFps] = useState(defaultConfig?.fps ?? (initialOutputFormat === "gif" ? 10 : 24));
137
+ const [orientation, setOrientation] = useState(
138
+ defaultConfig?.orientation ?? "landscape"
139
+ );
140
+ const [captionMode, setCaptionMode] = useState(defaultConfig?.captionMode ?? "off");
141
+ const [animationsEnabled, setAnimationsEnabled] = useState(
142
+ defaultConfig?.animationsEnabled ?? initialOutputFormat === "mp4"
143
+ );
144
+ const [audioPolicy, setAudioPolicy] = useState(
145
+ defaultConfig?.audioPolicy ?? "require"
146
+ );
147
+ const palette = {
148
+ ...VIDEO_EXPORT_PALETTES[colorScheme],
149
+ ...uiPalette
150
+ };
151
+ const themedModalStyle = {
152
+ ...modalStyle,
153
+ background: palette.surface,
154
+ border: `1px solid ${palette.border}`,
155
+ color: palette.text,
156
+ colorScheme
157
+ };
158
+ const themedTitleStyle = { ...titleStyle, color: palette.heading };
159
+ const themedLabelStyle = { ...labelStyle, color: palette.label };
160
+ const themedSelectStyle = {
161
+ ...selectStyle,
162
+ border: `1px solid ${palette.border}`,
163
+ background: palette.control,
164
+ color: palette.text,
165
+ colorScheme
166
+ };
167
+ const themedPrimaryButtonStyle = {
168
+ ...btnPrimary,
169
+ background: palette.primary,
170
+ border: `1px solid ${palette.primaryBorder}`,
171
+ color: palette.primaryText
172
+ };
173
+ const themedSecondaryButtonStyle = {
174
+ ...btnSecondary,
175
+ background: palette.secondary,
176
+ color: palette.text,
177
+ border: `1px solid ${palette.border}`
178
+ };
179
+ const exportHook = useVideoExport();
180
+ const {
181
+ state,
182
+ progress,
183
+ backend,
184
+ outputFormat: completedOutputFormat,
185
+ downloadUrl,
186
+ fileSize,
187
+ audioIncluded,
188
+ audioSkippedReason,
189
+ error,
190
+ elapsed,
191
+ estimatedRemaining,
192
+ startExport,
193
+ cancel: cancelExport,
194
+ reset: resetExport
195
+ } = exportHook;
196
+ const handleOutputFormatChange = useCallback((next) => {
197
+ setOutputFormat(next);
198
+ if (next === "gif") {
199
+ setFps(10);
200
+ setAnimationsEnabled(false);
201
+ } else {
202
+ setFps(24);
203
+ setAnimationsEnabled(true);
204
+ }
205
+ }, []);
206
+ const handleExport = useCallback(async () => {
207
+ const config = {
208
+ // defaultConfig is the base; explicit props/selections win over it.
209
+ ...defaultConfig,
210
+ outputFormat,
211
+ animationsEnabled,
212
+ quality,
213
+ fps,
214
+ orientation,
215
+ captionMode,
216
+ audioPolicy,
217
+ images,
218
+ audio,
219
+ mediaProvider,
220
+ // Only thread the bundle through when the host actually supplied one.
221
+ ...playerScript !== void 0 ? { playerScript } : {}
222
+ };
223
+ await startExport(doc, config);
224
+ }, [
225
+ doc,
226
+ outputFormat,
227
+ animationsEnabled,
228
+ quality,
229
+ fps,
230
+ orientation,
231
+ captionMode,
232
+ audioPolicy,
233
+ images,
234
+ audio,
235
+ mediaProvider,
236
+ playerScript,
237
+ defaultConfig,
238
+ startExport
239
+ ]);
240
+ const handleDownload = useCallback(() => {
241
+ if (!downloadUrl) return;
242
+ const a = document.createElement("a");
243
+ a.href = downloadUrl;
244
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
245
+ a.download = `document-${ts}.${completedOutputFormat}`;
246
+ document.body.appendChild(a);
247
+ a.click();
248
+ document.body.removeChild(a);
249
+ }, [downloadUrl, completedOutputFormat]);
250
+ const handleClose = useCallback(() => {
251
+ if (state === "capturing" || state === "encoding" || state === "preparing") {
252
+ cancelExport();
253
+ }
254
+ resetExport();
255
+ onClose();
256
+ }, [state, cancelExport, resetExport, onClose]);
257
+ const isExporting = state === "preparing" || state === "capturing" || state === "encoding";
258
+ useModalDialog({ rootRef: overlayRef, dialogRef, onClose: handleClose });
259
+ return /* @__PURE__ */ jsx(
260
+ "div",
261
+ {
262
+ ref: overlayRef,
263
+ style: { ...overlayStyle, background: palette.overlay },
264
+ "data-color-scheme": colorScheme,
265
+ onClick: handleClose,
266
+ children: /* @__PURE__ */ jsxs(
267
+ "div",
268
+ {
269
+ ref: dialogRef,
270
+ role: "dialog",
271
+ "aria-modal": "true",
272
+ "aria-labelledby": titleId,
273
+ tabIndex: -1,
274
+ style: themedModalStyle,
275
+ "data-squisq-video-export-modal": true,
276
+ onClick: (e) => e.stopPropagation(),
277
+ children: [
278
+ /* @__PURE__ */ jsx("h2", { id: titleId, style: themedTitleStyle, children: outputFormat === "gif" ? "Export Animated GIF" : "Export Video" }),
279
+ state === "idle" && /* @__PURE__ */ jsxs(Fragment, { children: [
280
+ /* @__PURE__ */ jsxs("div", { children: [
281
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Format" }),
282
+ /* @__PURE__ */ jsxs(
283
+ "select",
284
+ {
285
+ "aria-label": "Format",
286
+ style: themedSelectStyle,
287
+ value: outputFormat,
288
+ onChange: (e) => handleOutputFormatChange(e.target.value),
289
+ children: [
290
+ /* @__PURE__ */ jsx("option", { value: "mp4", children: "MP4 video" }),
291
+ /* @__PURE__ */ jsx("option", { value: "gif", children: "Animated GIF" })
292
+ ]
293
+ }
294
+ )
295
+ ] }),
296
+ /* @__PURE__ */ jsxs("div", { children: [
297
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Quality" }),
298
+ /* @__PURE__ */ jsxs(
299
+ "select",
300
+ {
301
+ "aria-label": "Quality",
302
+ style: themedSelectStyle,
303
+ value: quality,
304
+ onChange: (e) => setQuality(e.target.value),
305
+ children: [
306
+ /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft \u2014 fast, lower quality" }),
307
+ /* @__PURE__ */ jsx("option", { value: "normal", children: "Normal \u2014 balanced" }),
308
+ /* @__PURE__ */ jsx("option", { value: "high", children: "High \u2014 best quality, slower" })
309
+ ]
310
+ }
311
+ )
312
+ ] }),
313
+ /* @__PURE__ */ jsxs("div", { children: [
314
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Frame Rate" }),
315
+ /* @__PURE__ */ jsxs(
316
+ "select",
317
+ {
318
+ "aria-label": "Frame Rate",
319
+ style: themedSelectStyle,
320
+ value: fps,
321
+ onChange: (e) => setFps(Number(e.target.value)),
322
+ children: [
323
+ /* @__PURE__ */ jsx("option", { value: 10, children: "10 fps \u2014 recommended for GIF" }),
324
+ /* @__PURE__ */ jsx("option", { value: 15, children: "15 fps \u2014 fast export" }),
325
+ /* @__PURE__ */ jsx("option", { value: 24, children: "24 fps \u2014 cinematic" }),
326
+ /* @__PURE__ */ jsx("option", { value: 30, children: "30 fps \u2014 smooth" })
327
+ ]
328
+ }
329
+ )
330
+ ] }),
331
+ /* @__PURE__ */ jsxs("div", { children: [
332
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Orientation" }),
333
+ /* @__PURE__ */ jsxs(
334
+ "select",
335
+ {
336
+ "aria-label": "Orientation",
337
+ style: themedSelectStyle,
338
+ value: orientation,
339
+ onChange: (e) => setOrientation(e.target.value),
340
+ children: [
341
+ /* @__PURE__ */ jsxs("option", { value: "landscape", children: [
342
+ "Landscape (",
343
+ outputFormat === "gif" ? "960 \xD7 540" : "1920 \xD7 1080",
344
+ ")"
345
+ ] }),
346
+ /* @__PURE__ */ jsxs("option", { value: "portrait", children: [
347
+ "Portrait (",
348
+ outputFormat === "gif" ? "540 \xD7 960" : "1080 \xD7 1920",
349
+ ")"
350
+ ] })
351
+ ]
352
+ }
353
+ )
354
+ ] }),
355
+ /* @__PURE__ */ jsxs("div", { children: [
356
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Captions" }),
357
+ /* @__PURE__ */ jsxs(
358
+ "select",
359
+ {
360
+ "aria-label": "Captions",
361
+ style: themedSelectStyle,
362
+ value: captionMode,
363
+ onChange: (e) => setCaptionMode(e.target.value),
364
+ children: [
365
+ /* @__PURE__ */ jsx("option", { value: "off", children: "None" }),
366
+ /* @__PURE__ */ jsx("option", { value: "standard", children: "Standard (top bar)" }),
367
+ /* @__PURE__ */ jsx("option", { value: "social", children: "Social media (large words)" })
368
+ ]
369
+ }
370
+ )
371
+ ] }),
372
+ outputFormat === "mp4" && /* @__PURE__ */ jsxs("div", { children: [
373
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Audio" }),
374
+ /* @__PURE__ */ jsxs(
375
+ "select",
376
+ {
377
+ "aria-label": "Audio handling",
378
+ style: themedSelectStyle,
379
+ value: audioPolicy,
380
+ onChange: (e) => setAudioPolicy(e.target.value),
381
+ children: [
382
+ /* @__PURE__ */ jsx("option", { value: "require", children: "Require document audio" }),
383
+ /* @__PURE__ */ jsx("option", { value: "best-effort", children: "Best effort \u2014 allow video-only fallback" }),
384
+ /* @__PURE__ */ jsx("option", { value: "omit", children: "Omit audio intentionally" })
385
+ ]
386
+ }
387
+ )
388
+ ] }),
389
+ /* @__PURE__ */ jsxs("div", { children: [
390
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Animations & transitions" }),
391
+ /* @__PURE__ */ jsxs(
392
+ "select",
393
+ {
394
+ "aria-label": "Animations and transitions",
395
+ style: themedSelectStyle,
396
+ value: animationsEnabled ? "enabled" : "disabled",
397
+ onChange: (e) => setAnimationsEnabled(e.target.value === "enabled"),
398
+ children: [
399
+ /* @__PURE__ */ jsx("option", { value: "enabled", children: "Enabled" }),
400
+ /* @__PURE__ */ jsx("option", { value: "disabled", children: "Disabled \u2014 smaller files" })
401
+ ]
402
+ }
403
+ ),
404
+ outputFormat === "gif" && animationsEnabled && /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.muted, margin: "-6px 0 12px" }, children: "Disabling motion is recommended for much smaller GIFs." })
405
+ ] }),
406
+ /* @__PURE__ */ jsxs("div", { style: footerStyle, children: [
407
+ /* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: handleClose, children: "Cancel" }),
408
+ /* @__PURE__ */ jsx("button", { style: themedPrimaryButtonStyle, onClick: handleExport, children: outputFormat === "gif" ? "Export GIF" : "Export Video" })
409
+ ] })
410
+ ] }),
411
+ isExporting && /* @__PURE__ */ jsxs(Fragment, { children: [
412
+ backend && /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 8px 0" }, children: [
413
+ "Encoder: ",
414
+ encoderLabel(completedOutputFormat, backend)
415
+ ] }),
416
+ /* @__PURE__ */ jsx(
417
+ "div",
418
+ {
419
+ role: "progressbar",
420
+ "aria-label": "Video export progress",
421
+ "aria-valuemin": 0,
422
+ "aria-valuemax": 100,
423
+ "aria-valuenow": progress,
424
+ style: { ...progressBarOuterStyle, background: palette.secondary },
425
+ "data-squisq-video-export-progress-track": true,
426
+ children: /* @__PURE__ */ jsx(
427
+ "div",
428
+ {
429
+ style: {
430
+ width: `${progress}%`,
431
+ height: "100%",
432
+ background: palette.primary,
433
+ transition: "width 0.3s ease"
434
+ },
435
+ "data-squisq-video-export-progress": true
436
+ }
437
+ )
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: 13, margin: "0 0 4px 0" }, children: [
441
+ progress,
442
+ "% complete"
443
+ ] }),
444
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: palette.muted, margin: 0 }, children: [
445
+ formatDuration(elapsed),
446
+ " elapsed",
447
+ estimatedRemaining > 0 && ` \xB7 ~${formatDuration(estimatedRemaining)} remaining`
448
+ ] }),
449
+ /* @__PURE__ */ jsx("div", { style: footerStyle, children: /* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: cancelExport, children: "Cancel" }) })
450
+ ] }),
451
+ state === "complete" && /* @__PURE__ */ jsxs(Fragment, { children: [
452
+ /* @__PURE__ */ jsx("p", { style: { fontSize: 14, margin: "0 0 8px 0", color: palette.success }, children: "Export complete!" }),
453
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: 13, color: palette.label, margin: "0 0 4px 0" }, children: [
454
+ "File size: ",
455
+ (fileSize / (1024 * 1024)).toFixed(1),
456
+ " MB"
457
+ ] }),
458
+ completedOutputFormat === "gif" ? /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 4px 0" }, children: "Animated GIF does not include audio." }) : audioIncluded ? /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.success, margin: "0 0 4px 0" }, children: "Audio included \u2713" }) : /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 4px 0" }, children: [
459
+ "Video only",
460
+ audioSkippedReason ? ` \u2014 ${audioSkippedReason}` : ""
461
+ ] }),
462
+ backend && /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: palette.muted, margin: "0 0 12px 0" }, children: [
463
+ "Encoded with ",
464
+ encoderLabel(completedOutputFormat, backend)
465
+ ] }),
466
+ /* @__PURE__ */ jsxs("div", { style: footerStyle, children: [
467
+ /* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: handleClose, children: "Close" }),
468
+ /* @__PURE__ */ jsxs("button", { style: themedPrimaryButtonStyle, onClick: handleDownload, children: [
469
+ "Download ",
470
+ completedOutputFormat.toUpperCase()
471
+ ] })
472
+ ] })
473
+ ] }),
474
+ state === "error" && /* @__PURE__ */ jsxs(Fragment, { children: [
475
+ /* @__PURE__ */ jsx("p", { style: { fontSize: 14, margin: "0 0 8px 0", color: palette.danger }, children: "Export failed" }),
476
+ /* @__PURE__ */ jsx(
477
+ "p",
478
+ {
479
+ style: {
480
+ fontSize: 13,
481
+ color: palette.label,
482
+ margin: "0 0 12px 0",
483
+ wordBreak: "break-word"
484
+ },
485
+ children: error
486
+ }
487
+ ),
488
+ /* @__PURE__ */ jsxs("div", { style: footerStyle, children: [
489
+ /* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: handleClose, children: "Close" }),
490
+ /* @__PURE__ */ jsxs("button", { style: themedPrimaryButtonStyle, onClick: handleExport, children: [
491
+ "Retry ",
492
+ outputFormat === "gif" ? "GIF" : "video"
493
+ ] })
494
+ ] })
495
+ ] })
496
+ ]
497
+ }
498
+ )
499
+ }
500
+ );
501
+ }
502
+
503
+ // src/VideoExportButton.tsx
504
+ import { useState as useState2, useCallback as useCallback2 } from "react";
505
+ import { createPortal } from "react-dom";
506
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
507
+ function VideoExportButton({
508
+ doc,
509
+ playerScript,
510
+ mediaProvider,
511
+ images,
512
+ audio,
513
+ defaultConfig,
514
+ colorScheme,
515
+ uiPalette,
516
+ label,
517
+ style,
518
+ disabled
519
+ }) {
520
+ const [showModal, setShowModal] = useState2(false);
521
+ const resolvedLabel = label ?? (defaultConfig?.outputFormat === "gif" ? "Export GIF" : "Export Video");
522
+ const handleOpen = useCallback2(() => setShowModal(true), []);
523
+ const handleClose = useCallback2(() => setShowModal(false), []);
524
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
525
+ /* @__PURE__ */ jsx2("button", { onClick: handleOpen, disabled, style, children: resolvedLabel }),
526
+ showModal && createPortal(
527
+ /* @__PURE__ */ jsx2(
528
+ VideoExportModal,
529
+ {
530
+ doc,
531
+ playerScript,
532
+ mediaProvider,
533
+ images,
534
+ audio,
535
+ defaultConfig,
536
+ colorScheme,
537
+ uiPalette,
538
+ onClose: handleClose
539
+ }
540
+ ),
541
+ document.body
542
+ )
543
+ ] });
544
+ }
545
+
546
+ export {
547
+ VideoExportModal,
548
+ VideoExportButton
549
+ };