@bendyline/squisq-video-react 2.1.0 → 2.2.0
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 +23 -33
- package/dist/chunk-KQG6DZ7G.js +549 -0
- package/dist/chunk-VI5AIVOQ.js +1951 -0
- package/dist/chunk-VILZP3GL.js +856 -0
- package/dist/chunk-YZN7D4IC.js +300 -0
- package/dist/components/index.d.ts +90 -0
- package/dist/components/index.js +11 -0
- package/dist/encoder/index.d.ts +95 -0
- package/dist/encoder/index.js +13 -0
- package/dist/hooks/index.d.ts +32 -0
- package/dist/hooks/index.js +10 -0
- package/dist/index.d.ts +8 -315
- package/dist/index.js +12 -1664
- package/dist/useVideoExport-NtqZVgaz.d.ts +115 -0
- package/dist/workers/encode.worker.js +1 -1
- package/package.json +22 -7
- package/dist/chunk-2PGWBGAT.js +0 -46
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
|
|
8
|
-
|
|
9
|
-
| Package
|
|
10
|
-
|
|
|
11
|
-
| @
|
|
12
|
-
| @
|
|
13
|
-
| @
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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,5 @@ 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
|
-
|
|
47
|
-
|
|
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.
|
|
@@ -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
|
+
};
|