sanity-plugin-mux-input 2.15.0 → 2.17.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/dist/index.js +1150 -177
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1151 -178
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/actions/upload.ts +42 -4
- package/src/components/DraggableWatermark.tsx +877 -0
- package/src/components/PlayerActionsMenu.tsx +14 -0
- package/src/components/ResyncMetadata.tsx +152 -73
- package/src/components/TextTracksManager.tsx +11 -55
- package/src/components/UploadConfiguration.tsx +259 -59
- package/src/components/Uploader.tsx +7 -1
- package/src/components/VideoDetails/VideoDetails.tsx +27 -11
- package/src/components/VideoDetails/useVideoDetails.ts +15 -1
- package/src/components/VideoPlayer.tsx +2 -0
- package/src/hooks/useMediaMetadata.ts +3 -0
- package/src/hooks/useResyncAsset.ts +110 -0
- package/src/hooks/useResyncMuxMetadata.ts +33 -0
- package/src/schema.ts +5 -0
- package/src/util/addKeysToMuxData.ts +30 -0
- package/src/util/convertWatermarkToMux.ts +160 -0
- package/src/util/roundPxString.ts +16 -0
- package/src/util/types.ts +43 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useClient as useClient$1, createHookFromObservableFactory, useDocumentStore, collate, useDocumentValues, truncateString, useFormattedDuration, SanityDefaultPreview, useTimeAgo, TextWithTone, isRecord, getPreviewStateObservable, getPreviewValueWithFallback, DocumentPreviewPresence, useDocumentPreviewStore, useSchema, useDocumentPresence, PreviewCard, useCurrentUser, isReference, useProjectId, useDataset, PatchEvent, unset, setIfMissing, set, LinearProgress, FormField as FormField$2, definePlugin } from "sanity";
|
|
2
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { ErrorOutlineIcon, InfoOutlineIcon, RetryIcon, CheckmarkCircleIcon, RetrieveIcon, ChevronLeftIcon, ChevronRightIcon, SyncIcon, SortIcon, UploadIcon, TranslateIcon, DownloadIcon, AddIcon, ChevronUpIcon, ChevronDownIcon, TrashIcon, EditIcon, WarningOutlineIcon, PublishIcon, DocumentIcon, RevertIcon, SearchIcon, ClockIcon, CropIcon, CalendarIcon, TagIcon, CheckmarkIcon, LockIcon, PlayIcon, PlugIcon, EllipsisHorizontalIcon, ImageIcon, ResetIcon, WarningFilledIcon, DocumentVideoIcon } from "@sanity/icons";
|
|
4
|
-
import { Dialog, Stack, Card, Text, Button, useTheme_v2, Flex, Box, TextInput, Checkbox, Code, Inline, Spinner, Heading, Label as Label$1, MenuButton, Menu, MenuItem, useToast, Autocomplete, Tooltip, TabList, Tab, TabPanel, Grid, useClickOutsideEvent, Popover, MenuDivider,
|
|
4
|
+
import { Dialog, Stack, Card, Text, Button, useTheme_v2, Flex, Box, TextInput, Checkbox, Code, Inline, Spinner, Heading, Label as Label$1, Radio, MenuButton, Menu, MenuItem, useToast, Autocomplete, Tooltip, TabList, Tab, TabPanel, Grid, useClickOutsideEvent, Popover, MenuDivider, rem } from "@sanity/ui";
|
|
5
5
|
import React, { createContext, useContext, useState, useMemo, useCallback, useReducer, useId, memo, useRef, useEffect, Suspense, isValidElement, PureComponent, createElement, forwardRef } from "react";
|
|
6
6
|
import compact from "lodash/compact.js";
|
|
7
7
|
import toLower from "lodash/toLower.js";
|
|
@@ -973,7 +973,7 @@ function tryWithSuspend(block, onError) {
|
|
|
973
973
|
return onError ? onError(errorOrPromise) : void 0;
|
|
974
974
|
}
|
|
975
975
|
}
|
|
976
|
-
const Image = styled.img`
|
|
976
|
+
const Image$1 = styled.img`
|
|
977
977
|
transition: opacity 0.175s ease-out 0s;
|
|
978
978
|
display: block;
|
|
979
979
|
width: 100%;
|
|
@@ -1051,7 +1051,7 @@ function VideoThumbnail({
|
|
|
1051
1051
|
}
|
|
1052
1052
|
),
|
|
1053
1053
|
/* @__PURE__ */ jsx(
|
|
1054
|
-
Image,
|
|
1054
|
+
Image$1,
|
|
1055
1055
|
{
|
|
1056
1056
|
src: thumbnailSrc ?? void 0,
|
|
1057
1057
|
alt: `Preview for ${staticImage ? "image" : "video"} ${asset.filename || asset.assetId}`,
|
|
@@ -1321,6 +1321,26 @@ const PageSelector = (props) => {
|
|
|
1321
1321
|
)
|
|
1322
1322
|
] });
|
|
1323
1323
|
};
|
|
1324
|
+
function addKeysToMuxData(data) {
|
|
1325
|
+
return {
|
|
1326
|
+
...data,
|
|
1327
|
+
tracks: data.tracks?.map((track) => ({
|
|
1328
|
+
...track,
|
|
1329
|
+
_key: uuid()
|
|
1330
|
+
})),
|
|
1331
|
+
playback_ids: data.playback_ids?.map((playbackId) => ({
|
|
1332
|
+
...playbackId,
|
|
1333
|
+
_key: uuid()
|
|
1334
|
+
})),
|
|
1335
|
+
static_renditions: data.static_renditions ? {
|
|
1336
|
+
...data.static_renditions,
|
|
1337
|
+
files: data.static_renditions.files?.map((file) => ({
|
|
1338
|
+
...file,
|
|
1339
|
+
_key: uuid()
|
|
1340
|
+
}))
|
|
1341
|
+
} : void 0
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1324
1344
|
function useResyncMuxMetadata() {
|
|
1325
1345
|
const documentStore = useDocumentStore(), client = useClient$1({
|
|
1326
1346
|
apiVersion: SANITY_API_VERSION
|
|
@@ -1369,6 +1389,27 @@ function useResyncMuxMetadata() {
|
|
|
1369
1389
|
}
|
|
1370
1390
|
}
|
|
1371
1391
|
}
|
|
1392
|
+
async function syncFullData() {
|
|
1393
|
+
if (matchedAssets) {
|
|
1394
|
+
setResyncState("syncing");
|
|
1395
|
+
try {
|
|
1396
|
+
const tx = client.transaction();
|
|
1397
|
+
matchedAssets.forEach((matched) => {
|
|
1398
|
+
if (!matched.muxAsset) return;
|
|
1399
|
+
const dataWithKeys = addKeysToMuxData(matched.muxAsset);
|
|
1400
|
+
tx.patch(matched.sanityDoc._id, {
|
|
1401
|
+
set: {
|
|
1402
|
+
filename: matched.muxTitle || matched.currentTitle || "",
|
|
1403
|
+
status: matched.muxAsset.status,
|
|
1404
|
+
data: dataWithKeys
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
}), await tx.commit({ returnDocuments: !1 }), setResyncState("done");
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
setResyncState("error"), setResyncError(error);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1372
1413
|
return {
|
|
1373
1414
|
sanityAssetsLoading,
|
|
1374
1415
|
closeDialog,
|
|
@@ -1378,6 +1419,7 @@ function useResyncMuxMetadata() {
|
|
|
1378
1419
|
hasSecrets,
|
|
1379
1420
|
syncAllVideos,
|
|
1380
1421
|
syncOnlyEmpty,
|
|
1422
|
+
syncFullData,
|
|
1381
1423
|
matchedAssets,
|
|
1382
1424
|
muxAssets,
|
|
1383
1425
|
openDialog
|
|
@@ -1393,22 +1435,78 @@ const useSanityAssets = createHookFromObservableFactory(
|
|
|
1393
1435
|
}
|
|
1394
1436
|
)
|
|
1395
1437
|
);
|
|
1438
|
+
function OptionCard({
|
|
1439
|
+
id,
|
|
1440
|
+
selected,
|
|
1441
|
+
onSelect,
|
|
1442
|
+
title,
|
|
1443
|
+
count,
|
|
1444
|
+
description,
|
|
1445
|
+
disabled
|
|
1446
|
+
}) {
|
|
1447
|
+
return /* @__PURE__ */ jsx(
|
|
1448
|
+
Card,
|
|
1449
|
+
{
|
|
1450
|
+
as: "label",
|
|
1451
|
+
padding: 3,
|
|
1452
|
+
radius: 2,
|
|
1453
|
+
border: !0,
|
|
1454
|
+
tone: selected ? "primary" : "default",
|
|
1455
|
+
style: {
|
|
1456
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1457
|
+
opacity: disabled ? 0.5 : 1
|
|
1458
|
+
},
|
|
1459
|
+
children: /* @__PURE__ */ jsxs(Flex, { gap: 3, align: "flex-start", children: [
|
|
1460
|
+
/* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(
|
|
1461
|
+
Radio,
|
|
1462
|
+
{
|
|
1463
|
+
checked: selected,
|
|
1464
|
+
onChange: () => onSelect(id),
|
|
1465
|
+
disabled,
|
|
1466
|
+
name: "sync-option"
|
|
1467
|
+
}
|
|
1468
|
+
) }),
|
|
1469
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, flex: 1, children: [
|
|
1470
|
+
/* @__PURE__ */ jsx(Flex, { align: "center", gap: 2, children: /* @__PURE__ */ jsxs(Text, { size: 2, weight: "semibold", children: [
|
|
1471
|
+
title,
|
|
1472
|
+
" (",
|
|
1473
|
+
count,
|
|
1474
|
+
")"
|
|
1475
|
+
] }) }),
|
|
1476
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: description })
|
|
1477
|
+
] })
|
|
1478
|
+
] })
|
|
1479
|
+
}
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1396
1482
|
function ResyncMetadataDialog(props) {
|
|
1397
|
-
const { resyncState } = props,
|
|
1483
|
+
const { resyncState } = props, videosToUpdate = props.matchedAssets?.filter((m) => m.muxAsset).length || 0, videosWithEmptyOrPlaceholder = props.matchedAssets?.filter(
|
|
1398
1484
|
(m) => m.muxAsset && m.muxTitle && isEmptyOrPlaceholderTitle(m.currentTitle, m.muxAsset.id)
|
|
1399
|
-
).length || 0
|
|
1485
|
+
).length || 0, hasEmptyTitles = videosWithEmptyOrPlaceholder > 0, defaultOption = hasEmptyTitles ? "fillEmpty" : "syncTitles", [selectedOption, setSelectedOption] = useState(defaultOption), canTriggerResync = resyncState === "idle" || resyncState === "error", isResyncing = resyncState === "syncing", isDone = resyncState === "done", isLoading = props.muxAssets.loading || props.sanityAssetsLoading, handleSync = () => {
|
|
1486
|
+
switch (selectedOption) {
|
|
1487
|
+
case "fillEmpty":
|
|
1488
|
+
props.syncOnlyEmpty();
|
|
1489
|
+
break;
|
|
1490
|
+
case "syncTitles":
|
|
1491
|
+
props.syncAllVideos();
|
|
1492
|
+
break;
|
|
1493
|
+
case "fullResync":
|
|
1494
|
+
props.syncFullData();
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1400
1498
|
return /* @__PURE__ */ jsx(
|
|
1401
1499
|
Dialog,
|
|
1402
1500
|
{
|
|
1403
1501
|
animate: !0,
|
|
1404
|
-
header: "
|
|
1502
|
+
header: "Sync with Mux",
|
|
1405
1503
|
zOffset: DIALOGS_Z_INDEX,
|
|
1406
1504
|
id: "resync-metadata-dialog",
|
|
1407
1505
|
onClose: props.closeDialog,
|
|
1408
1506
|
onClickOutside: props.closeDialog,
|
|
1409
1507
|
width: 1,
|
|
1410
1508
|
position: "fixed",
|
|
1411
|
-
footer: !isDone && /* @__PURE__ */ jsx(Card, { padding: 3, children: /* @__PURE__ */ jsxs(Flex, { justify: "
|
|
1509
|
+
footer: !isDone && /* @__PURE__ */ jsx(Card, { padding: 3, children: /* @__PURE__ */ jsxs(Flex, { justify: "flex-end", gap: 2, children: [
|
|
1412
1510
|
/* @__PURE__ */ jsx(
|
|
1413
1511
|
Button,
|
|
1414
1512
|
{
|
|
@@ -1416,97 +1514,104 @@ function ResyncMetadataDialog(props) {
|
|
|
1416
1514
|
padding: 3,
|
|
1417
1515
|
mode: "ghost",
|
|
1418
1516
|
text: "Cancel",
|
|
1419
|
-
tone: "critical",
|
|
1420
1517
|
onClick: props.closeDialog,
|
|
1421
1518
|
disabled: isResyncing
|
|
1422
1519
|
}
|
|
1423
1520
|
),
|
|
1424
|
-
/* @__PURE__ */
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
/* @__PURE__ */ jsx(
|
|
1438
|
-
Button,
|
|
1439
|
-
{
|
|
1440
|
-
icon: SyncIcon,
|
|
1441
|
-
fontSize: 2,
|
|
1442
|
-
padding: 3,
|
|
1443
|
-
mode: "ghost",
|
|
1444
|
-
text: `Update all (${videosToUpdate})`,
|
|
1445
|
-
tone: "positive",
|
|
1446
|
-
onClick: props.syncAllVideos,
|
|
1447
|
-
iconRight: isResyncing && Spinner,
|
|
1448
|
-
disabled: !canTriggerResync
|
|
1449
|
-
}
|
|
1450
|
-
)
|
|
1451
|
-
] })
|
|
1521
|
+
/* @__PURE__ */ jsx(
|
|
1522
|
+
Button,
|
|
1523
|
+
{
|
|
1524
|
+
icon: SyncIcon,
|
|
1525
|
+
fontSize: 2,
|
|
1526
|
+
padding: 3,
|
|
1527
|
+
text: "Run sync",
|
|
1528
|
+
tone: "primary",
|
|
1529
|
+
onClick: handleSync,
|
|
1530
|
+
iconRight: isResyncing && Spinner,
|
|
1531
|
+
disabled: !canTriggerResync || isLoading
|
|
1532
|
+
}
|
|
1533
|
+
)
|
|
1452
1534
|
] }) }),
|
|
1453
1535
|
children: /* @__PURE__ */ jsxs(Box, { padding: 4, children: [
|
|
1454
|
-
|
|
1536
|
+
isLoading && /* @__PURE__ */ jsx(Card, { tone: "primary", marginBottom: 4, padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 4, children: [
|
|
1455
1537
|
/* @__PURE__ */ jsx(Spinner, { muted: !0, size: 4 }),
|
|
1456
1538
|
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1457
1539
|
/* @__PURE__ */ jsx(Text, { size: 2, weight: "semibold", children: "Loading assets from Mux" }),
|
|
1458
|
-
/* @__PURE__ */ jsx(Text, { size: 1, children: "This may take a while." })
|
|
1540
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "This may take a while." })
|
|
1459
1541
|
] })
|
|
1460
1542
|
] }) }),
|
|
1461
|
-
props.muxAssets.error && /* @__PURE__ */ jsx(Card, { tone: "critical", marginBottom:
|
|
1543
|
+
props.muxAssets.error && /* @__PURE__ */ jsx(Card, { tone: "critical", marginBottom: 4, padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
1462
1544
|
/* @__PURE__ */ jsx(ErrorOutlineIcon, { fontSize: 36 }),
|
|
1463
1545
|
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1464
1546
|
/* @__PURE__ */ jsx(Text, { size: 2, weight: "semibold", children: "There was an error getting data from Mux" }),
|
|
1465
1547
|
/* @__PURE__ */ jsx(Text, { size: 1, children: "Please try again or contact a developer for help." })
|
|
1466
1548
|
] })
|
|
1467
1549
|
] }) }),
|
|
1468
|
-
resyncState === "syncing" && /* @__PURE__ */ jsx(Card, { tone: "primary", marginBottom:
|
|
1550
|
+
resyncState === "syncing" && /* @__PURE__ */ jsx(Card, { tone: "primary", marginBottom: 4, padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 4, children: [
|
|
1469
1551
|
/* @__PURE__ */ jsx(Spinner, { muted: !0, size: 4 }),
|
|
1470
1552
|
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1471
|
-
/* @__PURE__ */ jsx(Text, { size: 2, weight: "semibold", children: "
|
|
1472
|
-
/* @__PURE__ */ jsx(Text, { size: 1, children: "
|
|
1553
|
+
/* @__PURE__ */ jsx(Text, { size: 2, weight: "semibold", children: "Syncing metadata" }),
|
|
1554
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Updating videos from Mux..." })
|
|
1473
1555
|
] })
|
|
1474
1556
|
] }) }),
|
|
1475
|
-
resyncState === "error" && /* @__PURE__ */ jsx(Card, { tone: "critical", marginBottom:
|
|
1557
|
+
resyncState === "error" && /* @__PURE__ */ jsx(Card, { tone: "critical", marginBottom: 4, padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
1476
1558
|
/* @__PURE__ */ jsx(ErrorOutlineIcon, { fontSize: 36 }),
|
|
1477
1559
|
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1478
1560
|
/* @__PURE__ */ jsx(Text, { size: 2, weight: "semibold", children: "There was an error syncing metadata" }),
|
|
1479
1561
|
/* @__PURE__ */ jsx(Text, { size: 1, children: props.resyncError ? `Error: ${props.resyncError}` : "Please try again or contact a developer for help." })
|
|
1480
1562
|
] })
|
|
1481
1563
|
] }) }),
|
|
1482
|
-
resyncState === "done" && /* @__PURE__ */ jsxs(Stack, { paddingY: 5,
|
|
1564
|
+
resyncState === "done" && /* @__PURE__ */ jsxs(Stack, { paddingY: 5, space: 3, style: { textAlign: "center" }, children: [
|
|
1483
1565
|
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(CheckmarkCircleIcon, { fontSize: 48 }) }),
|
|
1484
|
-
/* @__PURE__ */ jsx(Heading, { size: 2, children: "
|
|
1485
|
-
/* @__PURE__ */ jsx(Text, { size: 2, children: "
|
|
1566
|
+
/* @__PURE__ */ jsx(Heading, { size: 2, children: "Sync completed" }),
|
|
1567
|
+
/* @__PURE__ */ jsx(Text, { size: 2, muted: !0, children: "Videos have been updated from Mux." })
|
|
1486
1568
|
] }),
|
|
1487
|
-
|
|
1488
|
-
/* @__PURE__ */ jsxs(
|
|
1489
|
-
"
|
|
1490
|
-
videosToUpdate === 1 ? "is" : "are",
|
|
1491
|
-
" ",
|
|
1569
|
+
!isDone && !isLoading && !props.muxAssets.error && /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
1570
|
+
/* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
1571
|
+
"Found ",
|
|
1492
1572
|
videosToUpdate,
|
|
1493
1573
|
" video",
|
|
1494
1574
|
videosToUpdate === 1 ? "" : "s",
|
|
1495
|
-
"
|
|
1575
|
+
" linked to Mux."
|
|
1496
1576
|
] }),
|
|
1497
|
-
/* @__PURE__ */
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
"
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1577
|
+
/* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
1578
|
+
hasEmptyTitles && /* @__PURE__ */ jsx(
|
|
1579
|
+
OptionCard,
|
|
1580
|
+
{
|
|
1581
|
+
id: "fillEmpty",
|
|
1582
|
+
selected: selectedOption === "fillEmpty",
|
|
1583
|
+
onSelect: setSelectedOption,
|
|
1584
|
+
title: "Fill missing titles only",
|
|
1585
|
+
count: videosWithEmptyOrPlaceholder,
|
|
1586
|
+
description: "Updates only videos without a title or with placeholder titles (e.g., 'Asset #123') using the title from Mux.",
|
|
1587
|
+
disabled: isResyncing
|
|
1588
|
+
}
|
|
1589
|
+
),
|
|
1590
|
+
/* @__PURE__ */ jsx(
|
|
1591
|
+
OptionCard,
|
|
1592
|
+
{
|
|
1593
|
+
id: "syncTitles",
|
|
1594
|
+
selected: selectedOption === "syncTitles",
|
|
1595
|
+
onSelect: setSelectedOption,
|
|
1596
|
+
title: "Sync all titles",
|
|
1597
|
+
count: videosToUpdate,
|
|
1598
|
+
description: "Replaces the title in Sanity with the title from Mux for all videos.",
|
|
1599
|
+
disabled: isResyncing
|
|
1600
|
+
}
|
|
1601
|
+
),
|
|
1602
|
+
/* @__PURE__ */ jsx(
|
|
1603
|
+
OptionCard,
|
|
1604
|
+
{
|
|
1605
|
+
id: "fullResync",
|
|
1606
|
+
selected: selectedOption === "fullResync",
|
|
1607
|
+
onSelect: setSelectedOption,
|
|
1608
|
+
title: "Full resync",
|
|
1609
|
+
count: videosToUpdate,
|
|
1610
|
+
description: "Updates all fields from Mux including status, duration, tracks, captions, and renditions.",
|
|
1611
|
+
disabled: isResyncing
|
|
1612
|
+
}
|
|
1613
|
+
)
|
|
1614
|
+
] })
|
|
1510
1615
|
] })
|
|
1511
1616
|
] })
|
|
1512
1617
|
}
|
|
@@ -1515,7 +1620,7 @@ function ResyncMetadataDialog(props) {
|
|
|
1515
1620
|
function ResyncMetadata() {
|
|
1516
1621
|
const resyncMetadata = useResyncMuxMetadata();
|
|
1517
1622
|
if (resyncMetadata.hasSecrets)
|
|
1518
|
-
return resyncMetadata.dialogOpen ? /* @__PURE__ */ jsx(ResyncMetadataDialog, { ...resyncMetadata }) : /* @__PURE__ */ jsx(Button, { mode: "bleed", text: "
|
|
1623
|
+
return resyncMetadata.dialogOpen ? /* @__PURE__ */ jsx(ResyncMetadataDialog, { ...resyncMetadata }) : /* @__PURE__ */ jsx(Button, { mode: "bleed", text: "Sync with Mux", onClick: resyncMetadata.openDialog });
|
|
1519
1624
|
}
|
|
1520
1625
|
const CONTEXT_MENU_POPOVER_PROPS = {
|
|
1521
1626
|
constrainSize: !0,
|
|
@@ -1596,6 +1701,55 @@ function StopWatchIcon(props) {
|
|
|
1596
1701
|
}
|
|
1597
1702
|
);
|
|
1598
1703
|
}
|
|
1704
|
+
function useResyncAsset(options) {
|
|
1705
|
+
const client = useClient(), toast = useToast(), [resyncState, setResyncState] = useState("idle"), [resyncError, setResyncError] = useState(null), showToast = options?.showToast ?? !1, resyncAsset = useCallback(
|
|
1706
|
+
async (asset) => {
|
|
1707
|
+
if (!asset.assetId) {
|
|
1708
|
+
showToast && toast.push({
|
|
1709
|
+
title: "Cannot resync",
|
|
1710
|
+
description: "Asset has no Mux ID",
|
|
1711
|
+
status: "error"
|
|
1712
|
+
}), options?.onError?.(new Error("Asset has no Mux ID"));
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
if (!asset._id) {
|
|
1716
|
+
showToast && toast.push({
|
|
1717
|
+
title: "Cannot resync",
|
|
1718
|
+
description: "Asset has no document ID",
|
|
1719
|
+
status: "error"
|
|
1720
|
+
}), options?.onError?.(new Error("Asset has no document ID"));
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
setResyncState("syncing"), setResyncError(null);
|
|
1724
|
+
try {
|
|
1725
|
+
const muxData = (await getAsset(client, asset.assetId)).data, dataWithKeys = addKeysToMuxData(muxData);
|
|
1726
|
+
return await client.patch(asset._id).set({
|
|
1727
|
+
status: muxData.status,
|
|
1728
|
+
data: dataWithKeys,
|
|
1729
|
+
...muxData.meta?.title && { filename: muxData.meta.title }
|
|
1730
|
+
}).commit({ returnDocuments: !1 }), setResyncState("success"), showToast && toast.push({
|
|
1731
|
+
title: "Asset synced",
|
|
1732
|
+
description: "Data has been updated from Mux",
|
|
1733
|
+
status: "success"
|
|
1734
|
+
}), options?.onSuccess?.(muxData), muxData;
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
setResyncState("error"), setResyncError(error), console.error("Failed to refresh asset data:", error), showToast && toast.push({
|
|
1737
|
+
title: "Sync failed",
|
|
1738
|
+
description: "Could not sync asset from Mux",
|
|
1739
|
+
status: "error"
|
|
1740
|
+
}), options?.onError?.(error);
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
},
|
|
1744
|
+
[client, toast, options, showToast]
|
|
1745
|
+
);
|
|
1746
|
+
return {
|
|
1747
|
+
resyncState,
|
|
1748
|
+
resyncError,
|
|
1749
|
+
resyncAsset,
|
|
1750
|
+
isResyncing: resyncState === "syncing"
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1599
1753
|
function extractErrorMessage(error, defaultMessage = "Failed to process request") {
|
|
1600
1754
|
let message = "";
|
|
1601
1755
|
if (error && typeof error == "object") {
|
|
@@ -2498,20 +2652,7 @@ function TextTracksManager({
|
|
|
2498
2652
|
tracks: propTracks,
|
|
2499
2653
|
collapseTracks = !1
|
|
2500
2654
|
}) {
|
|
2501
|
-
const client = useClient(), toast = useToast(), dialogId = `DeleteCaptionDialog${useId()}`, [downloadingTrackId, setDownloadingTrackId] = useState(null), [deletingTrackId, setDeletingTrackId] = useState(null), [addedTracks, setAddedTracks] = useState([]), [updatedTracks, setUpdatedTracks] = useState(/* @__PURE__ */ new Map()), [trackActivityOrder, setTrackActivityOrder] = useState(/* @__PURE__ */ new Map()), [autogeneratedTrackIds, setAutogeneratedTrackIds] = useState(/* @__PURE__ */ new Set()), [trackToDelete, setTrackToDelete] = useState(null), [trackToEdit, setTrackToEdit] = useState(null), [showAddDialog, setShowAddDialog] = useState(!1), [isExpanded, setIsExpanded] = useState(!1), MAX_VISIBLE_TRACKS = 4
|
|
2502
|
-
useEffect(() => {
|
|
2503
|
-
if (!asset.assetId || !asset._id) return;
|
|
2504
|
-
const assetId = asset.assetId, documentId = asset._id;
|
|
2505
|
-
(async () => {
|
|
2506
|
-
try {
|
|
2507
|
-
const response = await getAsset(client, assetId);
|
|
2508
|
-
await client.patch(documentId).set({ data: response.data, status: response.data.status }).commit();
|
|
2509
|
-
} catch (error) {
|
|
2510
|
-
console.error("Failed to refresh asset data:", error);
|
|
2511
|
-
}
|
|
2512
|
-
})();
|
|
2513
|
-
}, [asset.assetId, asset._id, client]);
|
|
2514
|
-
const activeTracks = (propTracks || asset.data?.tracks?.filter((track) => track.type === "text") || []).filter(
|
|
2655
|
+
const client = useClient(), toast = useToast(), dialogId = `DeleteCaptionDialog${useId()}`, { resyncAsset } = useResyncAsset(), [downloadingTrackId, setDownloadingTrackId] = useState(null), [deletingTrackId, setDeletingTrackId] = useState(null), [addedTracks, setAddedTracks] = useState([]), [updatedTracks, setUpdatedTracks] = useState(/* @__PURE__ */ new Map()), [trackActivityOrder, setTrackActivityOrder] = useState(/* @__PURE__ */ new Map()), [autogeneratedTrackIds, setAutogeneratedTrackIds] = useState(/* @__PURE__ */ new Set()), [trackToDelete, setTrackToDelete] = useState(null), [trackToEdit, setTrackToEdit] = useState(null), [showAddDialog, setShowAddDialog] = useState(!1), [isExpanded, setIsExpanded] = useState(!1), MAX_VISIBLE_TRACKS = 4, activeTracks = (propTracks || asset.data?.tracks?.filter((track) => track.type === "text") || []).filter(
|
|
2515
2656
|
(track) => track.id && (track.status === "ready" || track.status === "preparing" || track.status === "errored")
|
|
2516
2657
|
), allTracks = useMemo(() => {
|
|
2517
2658
|
const tracksWithUpdates = activeTracks.map((track) => updatedTracks.get(track.id) || track), isMockTrackReplaced = (mockTrack, realTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : realTracksList.some((realTrack) => {
|
|
@@ -2542,11 +2683,11 @@ function TextTracksManager({
|
|
|
2542
2683
|
}, [activeTracks, addedTracks]), useEffect(() => {
|
|
2543
2684
|
if (allTracks.filter((track) => track.status === "preparing").length === 0 || !asset.assetId || !asset._id)
|
|
2544
2685
|
return;
|
|
2545
|
-
const
|
|
2686
|
+
const interval = setInterval(async () => {
|
|
2546
2687
|
try {
|
|
2547
|
-
const
|
|
2548
|
-
|
|
2549
|
-
const fetchedTracks =
|
|
2688
|
+
const muxData = await resyncAsset(asset);
|
|
2689
|
+
if (!muxData) return;
|
|
2690
|
+
const fetchedTracks = muxData.tracks?.filter((track) => track.type === "text") || [], isMockTrackReplaced = (mockTrack, fetchedTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : fetchedTracksList.some((realTrack) => {
|
|
2550
2691
|
const nameMatches = realTrack.name === mockTrack.name, languageMatches = realTrack.language_code === mockTrack.language_code;
|
|
2551
2692
|
return !nameMatches || !languageMatches ? !1 : realTrack.status === "ready" ? realTrack.text_source === "generated_live" || realTrack.text_source === "generated_live_final" || realTrack.text_source === "generated_vod" : realTrack.status === "preparing";
|
|
2552
2693
|
}), newAutogeneratedIds = /* @__PURE__ */ new Set();
|
|
@@ -2583,7 +2724,7 @@ function TextTracksManager({
|
|
|
2583
2724
|
}
|
|
2584
2725
|
}, 3e3);
|
|
2585
2726
|
return () => clearInterval(interval);
|
|
2586
|
-
}, [allTracks, asset
|
|
2727
|
+
}, [allTracks, asset, resyncAsset]);
|
|
2587
2728
|
const visibleTracks = allTracks.filter(
|
|
2588
2729
|
(track) => track.status === "ready" || track.status === "preparing" || track.status === "errored"
|
|
2589
2730
|
).sort((a2, b) => {
|
|
@@ -2619,14 +2760,7 @@ function TextTracksManager({
|
|
|
2619
2760
|
try {
|
|
2620
2761
|
if (!asset.assetId)
|
|
2621
2762
|
throw new Error("Asset ID is required");
|
|
2622
|
-
|
|
2623
|
-
try {
|
|
2624
|
-
const response = await getAsset(client, asset.assetId);
|
|
2625
|
-
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2626
|
-
} catch (refreshError) {
|
|
2627
|
-
console.error("Failed to refresh asset data:", refreshError);
|
|
2628
|
-
}
|
|
2629
|
-
toast.push({
|
|
2763
|
+
await deleteTextTrack(client, asset.assetId, track.id), await resyncAsset(asset), toast.push({
|
|
2630
2764
|
title: "Successfully deleted caption track",
|
|
2631
2765
|
status: "success"
|
|
2632
2766
|
}), setAddedTracks((prev) => prev.filter((t) => t.id !== track.id)), setUpdatedTracks((prev) => {
|
|
@@ -2654,7 +2788,7 @@ function TextTracksManager({
|
|
|
2654
2788
|
return newMap.set(track.id, prev.size + 1), newMap;
|
|
2655
2789
|
}), setShowAddDialog(!1);
|
|
2656
2790
|
}, handleUpdateTrack = async (updatedTrack, oldTrackId) => {
|
|
2657
|
-
|
|
2791
|
+
oldTrackId && (setAddedTracks((prev) => prev.filter((t) => t.id !== oldTrackId)), setUpdatedTracks((prev) => {
|
|
2658
2792
|
const newMap = new Map(prev);
|
|
2659
2793
|
return newMap.delete(oldTrackId), newMap;
|
|
2660
2794
|
}), setTrackActivityOrder((prev) => {
|
|
@@ -2669,13 +2803,7 @@ function TextTracksManager({
|
|
|
2669
2803
|
}), setTrackActivityOrder((prev) => {
|
|
2670
2804
|
const newMap = new Map(prev);
|
|
2671
2805
|
return newMap.set(updatedTrack.id, prev.size + 1), newMap;
|
|
2672
|
-
}), setTrackToEdit(null),
|
|
2673
|
-
try {
|
|
2674
|
-
const response = await getAsset(client, asset.assetId);
|
|
2675
|
-
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2676
|
-
} catch (refreshError) {
|
|
2677
|
-
console.error("Failed to refresh asset data:", refreshError);
|
|
2678
|
-
}
|
|
2806
|
+
}), setTrackToEdit(null), await resyncAsset(asset);
|
|
2679
2807
|
}, getTrackSourceLabel = (track) => track.id && track.id.startsWith("generating-") || track.id && autogeneratedTrackIds.has(track.id) || track.text_source === "generated_live_final" || track.text_source === "generated_live" || track.text_source === "generated_vod" ? "Auto-generated" : track.text_source === "uploaded" ? "Uploaded" : "Custom";
|
|
2680
2808
|
if (visibleTracks.length === 0 && !showAddDialog)
|
|
2681
2809
|
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
@@ -2931,7 +3059,7 @@ function VideoPlayer({
|
|
|
2931
3059
|
hlsConfig,
|
|
2932
3060
|
...props
|
|
2933
3061
|
}) {
|
|
2934
|
-
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = useRef(null), [error, setError] = useState(), playbackId = useMemo(() => {
|
|
3062
|
+
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = useRef(null), playerContainerRef = useRef(null), [error, setError] = useState(), playbackId = useMemo(() => {
|
|
2935
3063
|
try {
|
|
2936
3064
|
return getPlaybackId(asset, ["public", "signed", "drm"]);
|
|
2937
3065
|
} catch {
|
|
@@ -2989,6 +3117,7 @@ function VideoPlayer({
|
|
|
2989
3117
|
/* @__PURE__ */ jsxs(
|
|
2990
3118
|
Card,
|
|
2991
3119
|
{
|
|
3120
|
+
ref: playerContainerRef,
|
|
2992
3121
|
tone: "transparent",
|
|
2993
3122
|
style: {
|
|
2994
3123
|
aspectRatio,
|
|
@@ -3025,7 +3154,7 @@ function VideoPlayer({
|
|
|
3025
3154
|
crossOrigin: "anonymous",
|
|
3026
3155
|
metadata: {
|
|
3027
3156
|
player_name: "Sanity Admin Dashboard",
|
|
3028
|
-
player_version: "2.
|
|
3157
|
+
player_version: "2.17.0",
|
|
3029
3158
|
page_type: "Preview Player"
|
|
3030
3159
|
},
|
|
3031
3160
|
audio: isAudio,
|
|
@@ -3342,7 +3471,10 @@ function getVideoMetadata(doc) {
|
|
|
3342
3471
|
function useVideoDetails(props) {
|
|
3343
3472
|
const documentStore = useDocumentStore(), toast = useToast(), client = useClient(), [references, referencesLoading] = useDocReferences(
|
|
3344
3473
|
useMemo(() => ({ documentStore, id: props.asset._id }), [documentStore, props.asset._id])
|
|
3345
|
-
), [originalAsset, setOriginalAsset] = useState(() => props.asset), [filename, setFilename] = useState(props.asset.filename), modified = filename !== originalAsset.filename, displayInfo = getVideoMetadata({ ...props.asset, filename }), [state, setState] = useState("idle");
|
|
3474
|
+
), [originalAsset, setOriginalAsset] = useState(() => props.asset), [filename, setFilename] = useState(props.asset.filename), modified = filename !== originalAsset.filename, displayInfo = getVideoMetadata({ ...props.asset, filename }), [state, setState] = useState("idle"), { resyncAsset, isResyncing } = useResyncAsset({ showToast: !0 });
|
|
3475
|
+
async function handleResync() {
|
|
3476
|
+
state === "idle" && (setState("resyncing"), await resyncAsset(props.asset), setState("idle"));
|
|
3477
|
+
}
|
|
3346
3478
|
function handleClose() {
|
|
3347
3479
|
if (state === "idle") {
|
|
3348
3480
|
if (modified) {
|
|
@@ -3385,7 +3517,9 @@ function useVideoDetails(props) {
|
|
|
3385
3517
|
setState,
|
|
3386
3518
|
handleClose,
|
|
3387
3519
|
confirmClose,
|
|
3388
|
-
saveChanges
|
|
3520
|
+
saveChanges,
|
|
3521
|
+
handleResync,
|
|
3522
|
+
isResyncing
|
|
3389
3523
|
};
|
|
3390
3524
|
}
|
|
3391
3525
|
const AssetInput = (props) => /* @__PURE__ */ jsx(FormField$1, { title: props.label, description: props.description, inputId: props.label, children: /* @__PURE__ */ jsx(
|
|
@@ -3409,7 +3543,9 @@ const AssetInput = (props) => /* @__PURE__ */ jsx(FormField$1, { title: props.la
|
|
|
3409
3543
|
setState,
|
|
3410
3544
|
handleClose,
|
|
3411
3545
|
confirmClose,
|
|
3412
|
-
saveChanges
|
|
3546
|
+
saveChanges,
|
|
3547
|
+
handleResync,
|
|
3548
|
+
isResyncing
|
|
3413
3549
|
} = useVideoDetails(props), isSaving = state === "saving", [containerHeight, setContainerHeight] = useState(null), contentsRef = React.useRef(null);
|
|
3414
3550
|
return useEffect(() => {
|
|
3415
3551
|
!contentsRef.current || !("getBoundingClientRect" in contentsRef.current) || setContainerHeight(contentsRef.current.getBoundingClientRect().height);
|
|
@@ -3425,19 +3561,35 @@ const AssetInput = (props) => /* @__PURE__ */ jsx(FormField$1, { title: props.la
|
|
|
3425
3561
|
width: 2,
|
|
3426
3562
|
position: "fixed",
|
|
3427
3563
|
footer: /* @__PURE__ */ jsx(Card, { padding: 3, children: /* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
|
|
3428
|
-
/* @__PURE__ */
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3564
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
3565
|
+
/* @__PURE__ */ jsx(
|
|
3566
|
+
Button,
|
|
3567
|
+
{
|
|
3568
|
+
icon: TrashIcon,
|
|
3569
|
+
fontSize: 2,
|
|
3570
|
+
padding: 3,
|
|
3571
|
+
mode: "bleed",
|
|
3572
|
+
text: "Delete",
|
|
3573
|
+
tone: "critical",
|
|
3574
|
+
onClick: () => setState("deleting"),
|
|
3575
|
+
disabled: isSaving || isResyncing
|
|
3576
|
+
}
|
|
3577
|
+
),
|
|
3578
|
+
/* @__PURE__ */ jsx(
|
|
3579
|
+
Button,
|
|
3580
|
+
{
|
|
3581
|
+
icon: SyncIcon,
|
|
3582
|
+
fontSize: 2,
|
|
3583
|
+
padding: 3,
|
|
3584
|
+
mode: "bleed",
|
|
3585
|
+
text: "Resync",
|
|
3586
|
+
tone: "primary",
|
|
3587
|
+
onClick: handleResync,
|
|
3588
|
+
disabled: isSaving || isResyncing,
|
|
3589
|
+
iconRight: isResyncing && Spinner
|
|
3590
|
+
}
|
|
3591
|
+
)
|
|
3592
|
+
] }),
|
|
3441
3593
|
modified && /* @__PURE__ */ jsx(
|
|
3442
3594
|
Button,
|
|
3443
3595
|
{
|
|
@@ -3449,7 +3601,7 @@ const AssetInput = (props) => /* @__PURE__ */ jsx(FormField$1, { title: props.la
|
|
|
3449
3601
|
tone: "positive",
|
|
3450
3602
|
onClick: saveChanges,
|
|
3451
3603
|
iconRight: isSaving && Spinner,
|
|
3452
|
-
disabled: isSaving
|
|
3604
|
+
disabled: isSaving || isResyncing
|
|
3453
3605
|
}
|
|
3454
3606
|
)
|
|
3455
3607
|
] }) }),
|
|
@@ -4164,6 +4316,33 @@ function createUpChunkObservable(uuid2, uploadUrl2, source) {
|
|
|
4164
4316
|
return upchunk.on("success", successHandler), upchunk.on("error", errorHandler), upchunk.on("progress", progressHandler), upchunk.on("offline", offlineHandler), upchunk.on("online", onlineHandler), () => upchunk.abort();
|
|
4165
4317
|
});
|
|
4166
4318
|
}
|
|
4319
|
+
function roundPxString(value) {
|
|
4320
|
+
if (typeof value != "string") return;
|
|
4321
|
+
const trimmed = value.trim();
|
|
4322
|
+
if (!trimmed.endsWith("px")) return;
|
|
4323
|
+
const n = Number(trimmed.slice(0, -2));
|
|
4324
|
+
if (!Number.isFinite(n)) return;
|
|
4325
|
+
let rounded = Math.round(n);
|
|
4326
|
+
return rounded === 0 && (rounded = n < 0 ? -1 : 1), `${rounded}px`;
|
|
4327
|
+
}
|
|
4328
|
+
function sanitizeOverlaySettingsInPlace(settings) {
|
|
4329
|
+
const inputs = settings.input;
|
|
4330
|
+
if (inputs)
|
|
4331
|
+
for (const input of inputs) {
|
|
4332
|
+
const overlay = input.overlay_settings;
|
|
4333
|
+
if (!overlay) continue;
|
|
4334
|
+
const hm = roundPxString(overlay.horizontal_margin), vm = roundPxString(overlay.vertical_margin), w = roundPxString(overlay.width);
|
|
4335
|
+
hm && (overlay.horizontal_margin = hm), vm && (overlay.vertical_margin = vm), w && (overlay.width = w);
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
function sanitizePxStringsInJson(json) {
|
|
4339
|
+
return json.replace(/"(-?\d+(?:\.\d+)?)px"/g, (_match, num) => {
|
|
4340
|
+
const n = Number(num);
|
|
4341
|
+
if (!Number.isFinite(n)) return _match;
|
|
4342
|
+
let rounded = Math.round(n);
|
|
4343
|
+
return rounded === 0 && (rounded = n < 0 ? -1 : 1), `"${rounded}px"`;
|
|
4344
|
+
});
|
|
4345
|
+
}
|
|
4167
4346
|
function cancelUpload(client, uuid2) {
|
|
4168
4347
|
return client.observable.request({
|
|
4169
4348
|
url: `/addons/mux/uploads/${client.config().dataset}/${uuid2}`,
|
|
@@ -4174,7 +4353,8 @@ function cancelUpload(client, uuid2) {
|
|
|
4174
4353
|
function uploadUrl({
|
|
4175
4354
|
url,
|
|
4176
4355
|
settings,
|
|
4177
|
-
client
|
|
4356
|
+
client,
|
|
4357
|
+
watermark
|
|
4178
4358
|
}) {
|
|
4179
4359
|
return testUrl(url).pipe(
|
|
4180
4360
|
switchMap((validUrl) => concat(
|
|
@@ -4184,9 +4364,9 @@ function uploadUrl({
|
|
|
4184
4364
|
if (!json || !json.status)
|
|
4185
4365
|
return throwError(new Error("Invalid credentials"));
|
|
4186
4366
|
const uuid$1 = uuid(), muxBody = settings;
|
|
4187
|
-
muxBody.input || (muxBody.input = [{ type: "video" }]), muxBody.input[0].url = validUrl;
|
|
4367
|
+
muxBody.input || (muxBody.input = [{ type: "video" }]), muxBody.input[0].url = validUrl, sanitizeOverlaySettingsInPlace(muxBody);
|
|
4188
4368
|
const query = {
|
|
4189
|
-
muxBody: JSON.stringify(muxBody),
|
|
4369
|
+
muxBody: sanitizePxStringsInJson(JSON.stringify(muxBody)),
|
|
4190
4370
|
filename: validUrl.split("/").slice(-1)[0]
|
|
4191
4371
|
}, dataset = client.config().dataset;
|
|
4192
4372
|
return defer(
|
|
@@ -4214,7 +4394,8 @@ function uploadUrl({
|
|
|
4214
4394
|
function uploadFile({
|
|
4215
4395
|
settings,
|
|
4216
4396
|
client,
|
|
4217
|
-
file
|
|
4397
|
+
file,
|
|
4398
|
+
watermark
|
|
4218
4399
|
}) {
|
|
4219
4400
|
return testFile(file).pipe(
|
|
4220
4401
|
switchMap((fileOptions) => concat(
|
|
@@ -4224,7 +4405,7 @@ function uploadFile({
|
|
|
4224
4405
|
if (!json || !json.status)
|
|
4225
4406
|
return throwError(() => new Error("Invalid credentials"));
|
|
4226
4407
|
const uuid$1 = uuid(), body = settings;
|
|
4227
|
-
return concat(
|
|
4408
|
+
return sanitizeOverlaySettingsInPlace(body), concat(
|
|
4228
4409
|
of({ type: "uuid", uuid: uuid$1 }),
|
|
4229
4410
|
defer(
|
|
4230
4411
|
() => client.observable.request({
|
|
@@ -4278,7 +4459,7 @@ function pollUpload(client, uuid2) {
|
|
|
4278
4459
|
}, 2e3);
|
|
4279
4460
|
});
|
|
4280
4461
|
}
|
|
4281
|
-
async function updateAssetDocumentFromUpload(client, uuid2) {
|
|
4462
|
+
async function updateAssetDocumentFromUpload(client, uuid2, _watermark) {
|
|
4282
4463
|
let upload, asset;
|
|
4283
4464
|
try {
|
|
4284
4465
|
upload = await pollUpload(client, uuid2);
|
|
@@ -4643,7 +4824,9 @@ const FileButton = styled(MenuItem)(({ theme }) => {
|
|
|
4643
4824
|
color: white;
|
|
4644
4825
|
`, isVideoAsset = (asset) => asset._type === "mux.videoAsset";
|
|
4645
4826
|
function PlayerActionsMenu(props) {
|
|
4646
|
-
const { asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept } = props, [open, setOpen] = useState(!1), [menuElement, setMenuRef] = useState(null), isSigned = useMemo(() => getPlaybackPolicy(asset)?.policy === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange])
|
|
4827
|
+
const { asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept } = props, [open, setOpen] = useState(!1), [menuElement, setMenuRef] = useState(null), isSigned = useMemo(() => getPlaybackPolicy(asset)?.policy === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), { resyncAsset, isResyncing } = useResyncAsset({ showToast: !0 }), onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange]), handleResync = useCallback(async () => {
|
|
4828
|
+
setOpen(!1), await resyncAsset(asset);
|
|
4829
|
+
}, [resyncAsset, asset]);
|
|
4647
4830
|
return useEffect(() => {
|
|
4648
4831
|
open && dialogState && setOpen(!1);
|
|
4649
4832
|
}, [dialogState, open]), useClickOutsideEvent(
|
|
@@ -4701,6 +4884,15 @@ function PlayerActionsMenu(props) {
|
|
|
4701
4884
|
text: "Captions",
|
|
4702
4885
|
onClick: () => setDialogState("edit-captions")
|
|
4703
4886
|
}
|
|
4887
|
+
),
|
|
4888
|
+
/* @__PURE__ */ jsx(
|
|
4889
|
+
MenuItem,
|
|
4890
|
+
{
|
|
4891
|
+
icon: SyncIcon,
|
|
4892
|
+
text: "Resync from Mux",
|
|
4893
|
+
onClick: handleResync,
|
|
4894
|
+
disabled: readOnly || isResyncing
|
|
4895
|
+
}
|
|
4704
4896
|
)
|
|
4705
4897
|
] }),
|
|
4706
4898
|
/* @__PURE__ */ jsx(MenuDivider, {}),
|
|
@@ -4789,13 +4981,14 @@ function useMediaMetadata(stagedUpload) {
|
|
|
4789
4981
|
setIsLoadingMetadata(!1);
|
|
4790
4982
|
},
|
|
4791
4983
|
() => {
|
|
4792
|
-
const duration = videoElement.duration, width = videoElement.videoWidth, height = videoElement.videoHeight, isAudioOnly = width <= 0 && height <= 0;
|
|
4984
|
+
const duration = videoElement.duration, width = videoElement.videoWidth, height = videoElement.videoHeight, isAudioOnly = width <= 0 && height <= 0, aspectRatio = width / height;
|
|
4793
4985
|
setVideoAssetMetadata((old) => ({
|
|
4794
4986
|
...old,
|
|
4795
4987
|
duration,
|
|
4796
4988
|
width,
|
|
4797
4989
|
height,
|
|
4798
|
-
isAudioOnly
|
|
4990
|
+
isAudioOnly,
|
|
4991
|
+
aspectRatio
|
|
4799
4992
|
}));
|
|
4800
4993
|
}
|
|
4801
4994
|
], cleanupVideo = (videoEl) => {
|
|
@@ -4817,6 +5010,51 @@ function useMediaMetadata(stagedUpload) {
|
|
|
4817
5010
|
isLoadingMetadata
|
|
4818
5011
|
};
|
|
4819
5012
|
}
|
|
5013
|
+
function convertWatermarkToMuxOverlay(watermark, options) {
|
|
5014
|
+
if (!watermark.enabled || !watermark.imageUrl)
|
|
5015
|
+
return null;
|
|
5016
|
+
const size = watermark.size || 20, opacity = watermark.opacity ?? 0.7, toPxString = (valuePercent, axis) => {
|
|
5017
|
+
const videoAspectRatio2 = options?.videoAspectRatio ?? 1.7777777777777777, isVertical = videoAspectRatio2 > 0 && videoAspectRatio2 < 1, base = axis === "x" ? isVertical ? 1080 : 1920 : isVertical ? 1920 : 1080, px = valuePercent / 100 * base;
|
|
5018
|
+
let rounded = Math.round(px);
|
|
5019
|
+
return rounded === 0 && (rounded = px < 0 ? -1 : 1), `${rounded}px`;
|
|
5020
|
+
}, normalizeToPixels = (value, axis) => {
|
|
5021
|
+
if (!value) return value;
|
|
5022
|
+
const trimmed = value.trim();
|
|
5023
|
+
if (trimmed.endsWith("px"))
|
|
5024
|
+
return roundPxString(trimmed);
|
|
5025
|
+
if (trimmed.endsWith("%")) {
|
|
5026
|
+
const n = Number(trimmed.slice(0, -1));
|
|
5027
|
+
return Number.isFinite(n) ? toPxString(n, axis) : value;
|
|
5028
|
+
}
|
|
5029
|
+
return value;
|
|
5030
|
+
};
|
|
5031
|
+
if (watermark.overlay_settings) {
|
|
5032
|
+
const widthValue = watermark.overlay_settings.width, widthNormalized = options?.units === "px" ? normalizeToPixels(widthValue, "x") : widthValue;
|
|
5033
|
+
return {
|
|
5034
|
+
...watermark.overlay_settings,
|
|
5035
|
+
horizontal_margin: options?.units === "px" ? normalizeToPixels(watermark.overlay_settings.horizontal_margin, "x") ?? watermark.overlay_settings.horizontal_margin : watermark.overlay_settings.horizontal_margin,
|
|
5036
|
+
vertical_margin: options?.units === "px" ? normalizeToPixels(watermark.overlay_settings.vertical_margin, "y") ?? watermark.overlay_settings.vertical_margin : watermark.overlay_settings.vertical_margin,
|
|
5037
|
+
width: widthNormalized ?? `${size}%`,
|
|
5038
|
+
opacity: watermark.overlay_settings.opacity ?? `${Math.round(opacity * 100)}%`
|
|
5039
|
+
};
|
|
5040
|
+
}
|
|
5041
|
+
const position = watermark.position || { x: 50, y: 50 }, clampPercent = (value) => Math.max(-100, Math.min(100, value)), toPercentString = (value) => `${value === 0 || Object.is(value, -0) || Math.abs(value) < 1e-9 ? 0.01 : value}%`, watermarkWidthPercentOfVideoWidth = size, videoAspectRatio = options?.videoAspectRatio ?? 16 / 9, imageAspectRatio = watermark.imageAspectRatio ?? 1, watermarkHeightPercentOfVideoHeight = Math.max(
|
|
5042
|
+
0,
|
|
5043
|
+
Math.min(100, size * videoAspectRatio / imageAspectRatio)
|
|
5044
|
+
), halfWidth = watermarkWidthPercentOfVideoWidth / 2, halfHeight = watermarkHeightPercentOfVideoHeight / 2, leftMargin = clampPercent(
|
|
5045
|
+
Math.min(position.x - halfWidth, 100 - watermarkWidthPercentOfVideoWidth)
|
|
5046
|
+
), topMargin = clampPercent(
|
|
5047
|
+
Math.min(position.y - halfHeight, 100 - watermarkHeightPercentOfVideoHeight)
|
|
5048
|
+
), units = options?.units ?? "%", marginX = units === "px" ? toPxString(leftMargin, "x") : toPercentString(leftMargin), marginY = units === "px" ? toPxString(topMargin, "y") : toPercentString(topMargin), width = units === "px" ? toPxString(size, "x") : `${size}%`;
|
|
5049
|
+
return {
|
|
5050
|
+
vertical_align: "top",
|
|
5051
|
+
vertical_margin: marginY,
|
|
5052
|
+
horizontal_align: "left",
|
|
5053
|
+
horizontal_margin: marginX,
|
|
5054
|
+
width,
|
|
5055
|
+
opacity: `${Math.round(opacity * 100)}%`
|
|
5056
|
+
};
|
|
5057
|
+
}
|
|
4820
5058
|
function formatBytes(bytes, si = !1, dp = 1) {
|
|
4821
5059
|
const thresh = si ? 1e3 : 1024;
|
|
4822
5060
|
if (Math.abs(bytes) < thresh)
|
|
@@ -4829,6 +5067,566 @@ function formatBytes(bytes, si = !1, dp = 1) {
|
|
|
4829
5067
|
while (Math.round(Math.abs(bytes) * r) / r >= thresh && u2 < units.length - 1);
|
|
4830
5068
|
return bytes.toFixed(dp) + " " + units[u2];
|
|
4831
5069
|
}
|
|
5070
|
+
const RangeInput = styled.input`
|
|
5071
|
+
width: 100%;
|
|
5072
|
+
height: 4px;
|
|
5073
|
+
border-radius: 2px;
|
|
5074
|
+
background: var(--card-border-color);
|
|
5075
|
+
outline: none;
|
|
5076
|
+
-webkit-appearance: none;
|
|
5077
|
+
appearance: none;
|
|
5078
|
+
|
|
5079
|
+
&::-webkit-slider-thumb {
|
|
5080
|
+
-webkit-appearance: none;
|
|
5081
|
+
appearance: none;
|
|
5082
|
+
width: 16px;
|
|
5083
|
+
height: 16px;
|
|
5084
|
+
border-radius: 50%;
|
|
5085
|
+
background: var(--card-focus-ring-color, #2276fc);
|
|
5086
|
+
cursor: pointer;
|
|
5087
|
+
border: 2px solid white;
|
|
5088
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
&::-moz-range-thumb {
|
|
5092
|
+
width: 16px;
|
|
5093
|
+
height: 16px;
|
|
5094
|
+
border-radius: 50%;
|
|
5095
|
+
background: var(--card-focus-ring-color, #2276fc);
|
|
5096
|
+
cursor: pointer;
|
|
5097
|
+
border: 2px solid white;
|
|
5098
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
5099
|
+
}
|
|
5100
|
+
|
|
5101
|
+
&:hover::-webkit-slider-thumb {
|
|
5102
|
+
background: var(--card-focus-ring-color, #1a5fc7);
|
|
5103
|
+
}
|
|
5104
|
+
|
|
5105
|
+
&:hover::-moz-range-thumb {
|
|
5106
|
+
background: var(--card-focus-ring-color, #1a5fc7);
|
|
5107
|
+
}
|
|
5108
|
+
`, WatermarkOverlay = styled.div`
|
|
5109
|
+
position: absolute;
|
|
5110
|
+
max-width: 200px;
|
|
5111
|
+
opacity: ${(props) => props.$opacity};
|
|
5112
|
+
cursor: move;
|
|
5113
|
+
user-select: none;
|
|
5114
|
+
z-index: 10;
|
|
5115
|
+
pointer-events: auto;
|
|
5116
|
+
|
|
5117
|
+
img {
|
|
5118
|
+
width: 100%;
|
|
5119
|
+
height: auto;
|
|
5120
|
+
display: block;
|
|
5121
|
+
pointer-events: none;
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5124
|
+
&:hover {
|
|
5125
|
+
outline: 2px dashed rgba(255, 255, 255, 0.8);
|
|
5126
|
+
outline-offset: 4px;
|
|
5127
|
+
}
|
|
5128
|
+
`;
|
|
5129
|
+
function DraggableWatermark({
|
|
5130
|
+
watermark,
|
|
5131
|
+
onChange,
|
|
5132
|
+
containerRef,
|
|
5133
|
+
videoElementRef
|
|
5134
|
+
}) {
|
|
5135
|
+
const [isDragging, setIsDragging] = useState(!1), [dragStart, setDragStart] = useState({ x: 0, y: 0 }), [startPosition, setStartPosition] = useState({ x: 0, y: 0 }), watermarkRef = useRef(null), debounceTimeoutRef = useRef(null), [localPosition, setLocalPosition] = useState(watermark.position || { x: 50, y: 50 }), position = localPosition, size = watermark.size || 20, opacity = watermark.opacity ?? 0.7, parseOpacityPercent = (value) => {
|
|
5136
|
+
if (!value) return null;
|
|
5137
|
+
const trimmed = value.trim();
|
|
5138
|
+
if (!trimmed.endsWith("%")) return null;
|
|
5139
|
+
const num = Number(trimmed.slice(0, -1));
|
|
5140
|
+
return Number.isFinite(num) ? Math.max(0, Math.min(1, num / 100)) : null;
|
|
5141
|
+
}, getVideoContentBox = useCallback(() => {
|
|
5142
|
+
const container = containerRef?.current;
|
|
5143
|
+
if (!container) return { x: 0, y: 0, width: 0, height: 0 };
|
|
5144
|
+
const rect = container.getBoundingClientRect(), containerW = rect.width, containerH = rect.height, videoEl = videoElementRef?.current, videoW = videoEl?.videoWidth || 0, videoH = videoEl?.videoHeight || 0;
|
|
5145
|
+
if (!videoW || !videoH || !containerW || !containerH)
|
|
5146
|
+
return { x: 0, y: 0, width: containerW, height: containerH };
|
|
5147
|
+
const scale = Math.min(containerW / videoW, containerH / videoH), contentW = videoW * scale, contentH = videoH * scale, offsetX = (containerW - contentW) / 2, offsetY = (containerH - contentH) / 2;
|
|
5148
|
+
return { x: offsetX, y: offsetY, width: contentW, height: contentH };
|
|
5149
|
+
}, [containerRef, videoElementRef]), parseOverlayValue = (value) => {
|
|
5150
|
+
if (!value) return null;
|
|
5151
|
+
const trimmed = value.trim(), px = trimmed.endsWith("px"), pct = trimmed.endsWith("%"), num = Number(trimmed.replace(/px|%/g, ""));
|
|
5152
|
+
return Number.isFinite(num) ? px ? { n: num, unit: "px" } : pct ? { n: num, unit: "%" } : null : null;
|
|
5153
|
+
}, computeManualStyle = (overlay) => {
|
|
5154
|
+
const rect = containerRef?.current?.getBoundingClientRect(), w = rect?.width ?? 0, h = rect?.height ?? 0, isVertical = h > w, baseW = isVertical ? 1080 : 1920, baseH = isVertical ? 1920 : 1080, hm = parseOverlayValue(overlay.horizontal_margin), vm = parseOverlayValue(overlay.vertical_margin), ww = parseOverlayValue(overlay.width), manualOpacity = parseOpacityPercent(overlay.opacity), toCss = (v, axis) => {
|
|
5155
|
+
if (v)
|
|
5156
|
+
return v.unit === "%" ? `${v.n}%` : axis === "x" ? `${v.n * w / baseW}px` : `${v.n * h / baseH}px`;
|
|
5157
|
+
}, computeHorizontalStyle = () => overlay.horizontal_align === "left" ? { left: toCss(hm, "x"), right: void 0, transform: "translate(0, 0)" } : overlay.horizontal_align === "right" ? { right: toCss(hm, "x"), left: void 0, transform: "translate(0, 0)" } : { left: "50%", right: void 0, transform: "translate(-50%, 0)" }, computeVerticalStyle = () => overlay.vertical_align === "top" ? { top: toCss(vm, "y"), bottom: void 0 } : overlay.vertical_align === "bottom" ? { bottom: toCss(vm, "y"), top: void 0 } : { top: "50%", bottom: void 0 }, hStyle = computeHorizontalStyle(), vStyle = computeVerticalStyle();
|
|
5158
|
+
let transform = hStyle.transform;
|
|
5159
|
+
return overlay.vertical_align === "middle" && (transform = overlay.horizontal_align === "center" ? "translate(-50%, -50%)" : "translate(0, -50%)"), {
|
|
5160
|
+
position: "absolute",
|
|
5161
|
+
...hStyle,
|
|
5162
|
+
...vStyle,
|
|
5163
|
+
transform,
|
|
5164
|
+
width: ww ? toCss(ww, "x") : `${size}%`,
|
|
5165
|
+
opacity: manualOpacity ?? opacity,
|
|
5166
|
+
cursor: "default"
|
|
5167
|
+
};
|
|
5168
|
+
}, debouncedOnChange = useCallback(
|
|
5169
|
+
(newWatermark) => {
|
|
5170
|
+
debounceTimeoutRef.current && clearTimeout(debounceTimeoutRef.current), debounceTimeoutRef.current = setTimeout(() => {
|
|
5171
|
+
onChange(newWatermark);
|
|
5172
|
+
}, 300);
|
|
5173
|
+
},
|
|
5174
|
+
[onChange]
|
|
5175
|
+
);
|
|
5176
|
+
useEffect(() => () => {
|
|
5177
|
+
debounceTimeoutRef.current && clearTimeout(debounceTimeoutRef.current);
|
|
5178
|
+
}, []), useEffect(() => {
|
|
5179
|
+
!isDragging && watermark.position && setLocalPosition(watermark.position);
|
|
5180
|
+
}, [watermark.position, isDragging]);
|
|
5181
|
+
const handleMouseDown = useCallback(
|
|
5182
|
+
(e) => {
|
|
5183
|
+
e.preventDefault(), setIsDragging(!0), setDragStart({ x: e.clientX, y: e.clientY }), setStartPosition({ x: position.x, y: position.y });
|
|
5184
|
+
},
|
|
5185
|
+
[position]
|
|
5186
|
+
), handleMouseMove = useCallback(
|
|
5187
|
+
(e) => {
|
|
5188
|
+
if (!isDragging || !containerRef?.current) return;
|
|
5189
|
+
const rect = containerRef.current.getBoundingClientRect(), content = getVideoContentBox(), contentW = content.width || rect.width, contentH = content.height || rect.height, dx = e.clientX - dragStart.x, dy = e.clientY - dragStart.y, deltaXPercent = dx / contentW * 100, deltaYPercent = dy / contentH * 100;
|
|
5190
|
+
let newX = startPosition.x + deltaXPercent, newY = startPosition.y + deltaYPercent;
|
|
5191
|
+
newX = Math.max(0, Math.min(100, newX)), newY = Math.max(0, Math.min(100, newY)), setLocalPosition({ x: newX, y: newY }), debouncedOnChange({
|
|
5192
|
+
...watermark,
|
|
5193
|
+
position: { x: newX, y: newY }
|
|
5194
|
+
});
|
|
5195
|
+
},
|
|
5196
|
+
[
|
|
5197
|
+
isDragging,
|
|
5198
|
+
dragStart,
|
|
5199
|
+
startPosition,
|
|
5200
|
+
containerRef,
|
|
5201
|
+
watermark,
|
|
5202
|
+
debouncedOnChange,
|
|
5203
|
+
getVideoContentBox
|
|
5204
|
+
]
|
|
5205
|
+
), handleMouseUp = useCallback(() => {
|
|
5206
|
+
setIsDragging(!1), debounceTimeoutRef.current && (clearTimeout(debounceTimeoutRef.current), debounceTimeoutRef.current = null), onChange({
|
|
5207
|
+
...watermark,
|
|
5208
|
+
position: localPosition
|
|
5209
|
+
});
|
|
5210
|
+
}, [watermark, localPosition, onChange]);
|
|
5211
|
+
if (useEffect(() => {
|
|
5212
|
+
if (isDragging)
|
|
5213
|
+
return document.addEventListener("mousemove", handleMouseMove), document.addEventListener("mouseup", handleMouseUp), () => {
|
|
5214
|
+
document.removeEventListener("mousemove", handleMouseMove), document.removeEventListener("mouseup", handleMouseUp);
|
|
5215
|
+
};
|
|
5216
|
+
}, [isDragging, handleMouseMove, handleMouseUp]), !watermark.imageUrl)
|
|
5217
|
+
return null;
|
|
5218
|
+
const hasManualOverlay = !!watermark.overlay_settings, opacityForRender = hasManualOverlay ? parseOpacityPercent(watermark.overlay_settings?.opacity) ?? opacity : opacity, contentBox = getVideoContentBox(), hasContentBox = contentBox.width > 0 && contentBox.height > 0;
|
|
5219
|
+
return /* @__PURE__ */ jsx(
|
|
5220
|
+
WatermarkOverlay,
|
|
5221
|
+
{
|
|
5222
|
+
ref: watermarkRef,
|
|
5223
|
+
$opacity: opacityForRender,
|
|
5224
|
+
onMouseDown: hasManualOverlay ? void 0 : handleMouseDown,
|
|
5225
|
+
style: hasManualOverlay ? computeManualStyle(watermark.overlay_settings) : hasContentBox ? {
|
|
5226
|
+
left: `${contentBox.x + position.x / 100 * contentBox.width}px`,
|
|
5227
|
+
top: `${contentBox.y + position.y / 100 * contentBox.height}px`,
|
|
5228
|
+
transform: "translate(-50%, -50%)",
|
|
5229
|
+
width: `${Math.max(1, size / 100 * contentBox.width)}px`,
|
|
5230
|
+
cursor: isDragging ? "grabbing" : "grab"
|
|
5231
|
+
} : {
|
|
5232
|
+
left: `${position.x}%`,
|
|
5233
|
+
top: `${position.y}%`,
|
|
5234
|
+
transform: "translate(-50%, -50%)",
|
|
5235
|
+
width: `${size}%`,
|
|
5236
|
+
cursor: isDragging ? "grabbing" : "grab"
|
|
5237
|
+
},
|
|
5238
|
+
children: /* @__PURE__ */ jsx("img", { src: watermark.imageUrl, alt: "Watermark", draggable: !1 })
|
|
5239
|
+
}
|
|
5240
|
+
);
|
|
5241
|
+
}
|
|
5242
|
+
function WatermarkControls({
|
|
5243
|
+
watermark,
|
|
5244
|
+
onChange,
|
|
5245
|
+
onValidationChange,
|
|
5246
|
+
previewContainerRef,
|
|
5247
|
+
previewVideoRef
|
|
5248
|
+
}) {
|
|
5249
|
+
const [urlInput, setUrlInput] = useState(watermark.imageUrl || ""), [urlError, setUrlError] = useState(null), [isValidating, setIsValidating] = useState(!1), [isValid, setIsValid] = useState(null), validationTimeoutRef = useRef(null), [mode, setMode] = useState(
|
|
5250
|
+
watermark.overlay_settings ? "manual" : "canvas"
|
|
5251
|
+
), isUpdatingRef = useRef(!1), isValidExtension = (extension) => extension.endsWith(".png") || extension.endsWith(".jpg") || extension.endsWith(".jpeg"), validateUrl = useCallback(
|
|
5252
|
+
(url) => {
|
|
5253
|
+
if (validationTimeoutRef.current && clearTimeout(validationTimeoutRef.current), !url) {
|
|
5254
|
+
setUrlError(null), setIsValid(null), setIsValidating(!1), onValidationChange?.(null), isUpdatingRef.current = !0, onChange({
|
|
5255
|
+
...watermark,
|
|
5256
|
+
enabled: !1,
|
|
5257
|
+
imageUrl: void 0,
|
|
5258
|
+
overlay_settings: void 0
|
|
5259
|
+
});
|
|
5260
|
+
return;
|
|
5261
|
+
}
|
|
5262
|
+
setIsValidating(!0), setIsValid(null), setUrlError(null), validationTimeoutRef.current = setTimeout(() => {
|
|
5263
|
+
try {
|
|
5264
|
+
const pathname = new URL(url).pathname.toLowerCase();
|
|
5265
|
+
if (isValidExtension(pathname)) {
|
|
5266
|
+
setIsValid(!0), setUrlError(null), onValidationChange?.(null);
|
|
5267
|
+
const img = new Image();
|
|
5268
|
+
img.onload = () => {
|
|
5269
|
+
const imageAspectRatio = img.naturalWidth && img.naturalHeight ? img.naturalWidth / img.naturalHeight : 1;
|
|
5270
|
+
isUpdatingRef.current = !0, onChange({
|
|
5271
|
+
...watermark,
|
|
5272
|
+
enabled: !0,
|
|
5273
|
+
imageUrl: url,
|
|
5274
|
+
imageAspectRatio
|
|
5275
|
+
});
|
|
5276
|
+
}, img.onerror = () => {
|
|
5277
|
+
isUpdatingRef.current = !0, onChange({
|
|
5278
|
+
...watermark,
|
|
5279
|
+
enabled: !0,
|
|
5280
|
+
imageUrl: url,
|
|
5281
|
+
imageAspectRatio: watermark.imageAspectRatio
|
|
5282
|
+
});
|
|
5283
|
+
}, img.src = url;
|
|
5284
|
+
} else {
|
|
5285
|
+
const errorMsg = "Mux only supports PNG and JPG watermark images. Please use a .png or .jpg file.";
|
|
5286
|
+
setIsValid(!1), setUrlError(errorMsg), onValidationChange?.(errorMsg), isUpdatingRef.current = !0, onChange({
|
|
5287
|
+
...watermark,
|
|
5288
|
+
enabled: !1,
|
|
5289
|
+
imageUrl: void 0,
|
|
5290
|
+
imageAspectRatio: void 0,
|
|
5291
|
+
overlay_settings: void 0
|
|
5292
|
+
});
|
|
5293
|
+
}
|
|
5294
|
+
} catch {
|
|
5295
|
+
setIsValid(!1);
|
|
5296
|
+
const errorMsg = "Please enter a valid URL (e.g., https://example.com/watermark.png)";
|
|
5297
|
+
setUrlError(errorMsg), onValidationChange?.(errorMsg), isUpdatingRef.current = !0, onChange({
|
|
5298
|
+
...watermark,
|
|
5299
|
+
enabled: !1,
|
|
5300
|
+
imageUrl: void 0,
|
|
5301
|
+
imageAspectRatio: void 0,
|
|
5302
|
+
overlay_settings: void 0
|
|
5303
|
+
});
|
|
5304
|
+
} finally {
|
|
5305
|
+
setIsValidating(!1);
|
|
5306
|
+
}
|
|
5307
|
+
}, 500);
|
|
5308
|
+
},
|
|
5309
|
+
[watermark, onChange, onValidationChange]
|
|
5310
|
+
);
|
|
5311
|
+
useEffect(() => () => {
|
|
5312
|
+
validationTimeoutRef.current && clearTimeout(validationTimeoutRef.current);
|
|
5313
|
+
}, []), useEffect(() => {
|
|
5314
|
+
setMode(watermark.overlay_settings ? "manual" : "canvas");
|
|
5315
|
+
}, [watermark.overlay_settings]);
|
|
5316
|
+
const handleUrlChange = (e) => {
|
|
5317
|
+
const url = e.target.value;
|
|
5318
|
+
setUrlInput(url), watermark.imageUrl && url !== watermark.imageUrl && (isUpdatingRef.current = !0, onChange({
|
|
5319
|
+
...watermark,
|
|
5320
|
+
enabled: !1,
|
|
5321
|
+
imageUrl: void 0,
|
|
5322
|
+
imageAspectRatio: void 0,
|
|
5323
|
+
overlay_settings: void 0
|
|
5324
|
+
})), validateUrl(url);
|
|
5325
|
+
}, normalizeZeroPercent = (value) => {
|
|
5326
|
+
if (!value) return value;
|
|
5327
|
+
const trimmed = value.trim();
|
|
5328
|
+
if (!trimmed.endsWith("%")) return value;
|
|
5329
|
+
const n = Number(trimmed.slice(0, -1));
|
|
5330
|
+
return Number.isFinite(n) ? n === 0 || Object.is(n, -0) || Math.abs(n) < 1e-9 ? "0.01%" : `${n}%` : value;
|
|
5331
|
+
}, updateOverlaySettings = (next) => {
|
|
5332
|
+
const merged = {
|
|
5333
|
+
...watermark.overlay_settings ?? {
|
|
5334
|
+
vertical_align: "bottom",
|
|
5335
|
+
vertical_margin: "2%",
|
|
5336
|
+
horizontal_align: "right",
|
|
5337
|
+
horizontal_margin: "2%",
|
|
5338
|
+
width: `${watermark.size ?? 20}%`,
|
|
5339
|
+
opacity: `${Math.round((watermark.opacity ?? 0.7) * 100)}%`
|
|
5340
|
+
},
|
|
5341
|
+
...next
|
|
5342
|
+
};
|
|
5343
|
+
onChange({
|
|
5344
|
+
...watermark,
|
|
5345
|
+
enabled: !0,
|
|
5346
|
+
overlay_settings: {
|
|
5347
|
+
...merged,
|
|
5348
|
+
horizontal_margin: normalizeZeroPercent(merged.horizontal_margin) || merged.horizontal_margin,
|
|
5349
|
+
vertical_margin: normalizeZeroPercent(merged.vertical_margin) || merged.vertical_margin
|
|
5350
|
+
}
|
|
5351
|
+
});
|
|
5352
|
+
}, getVideoContentBox = () => {
|
|
5353
|
+
const container = previewContainerRef?.current;
|
|
5354
|
+
if (!container) return { x: 0, y: 0, width: 0, height: 0 };
|
|
5355
|
+
const rect = container.getBoundingClientRect(), containerW = rect.width, containerH = rect.height, videoEl = previewVideoRef?.current, videoW = videoEl?.videoWidth || 0, videoH = videoEl?.videoHeight || 0;
|
|
5356
|
+
if (!videoW || !videoH || !containerW || !containerH)
|
|
5357
|
+
return { x: 0, y: 0, width: containerW, height: containerH };
|
|
5358
|
+
const scale = Math.min(containerW / videoW, containerH / videoH), contentW = videoW * scale, contentH = videoH * scale, offsetX = (containerW - contentW) / 2, offsetY = (containerH - contentH) / 2;
|
|
5359
|
+
return { x: offsetX, y: offsetY, width: contentW, height: contentH };
|
|
5360
|
+
};
|
|
5361
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
5362
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
5363
|
+
/* @__PURE__ */ jsx(Text, { size: 1, weight: "medium", children: "Watermark Image URL" }),
|
|
5364
|
+
/* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: "Enter a URL to a PNG or JPG image. Mux will download this image and overlay it on your video." }),
|
|
5365
|
+
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", width: "100%" }, children: [
|
|
5366
|
+
/* @__PURE__ */ jsx(
|
|
5367
|
+
"input",
|
|
5368
|
+
{
|
|
5369
|
+
type: "url",
|
|
5370
|
+
value: urlInput,
|
|
5371
|
+
onChange: handleUrlChange,
|
|
5372
|
+
placeholder: "https://example.com/watermark.png",
|
|
5373
|
+
style: {
|
|
5374
|
+
padding: "8px 12px",
|
|
5375
|
+
paddingRight: urlInput ? "96px" : isValid !== null ? "36px" : "12px",
|
|
5376
|
+
border: urlError || isValid === !1 ? "1px solid #e74c3c" : isValid === !0 ? "1px solid #4caf50" : "1px solid #ccc",
|
|
5377
|
+
borderRadius: "4px",
|
|
5378
|
+
width: "100%",
|
|
5379
|
+
maxWidth: "100%",
|
|
5380
|
+
boxSizing: "border-box",
|
|
5381
|
+
fontSize: "14px"
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
),
|
|
5385
|
+
(urlInput || isValidating || isValid !== null) && /* @__PURE__ */ jsxs(
|
|
5386
|
+
Box,
|
|
5387
|
+
{
|
|
5388
|
+
style: {
|
|
5389
|
+
position: "absolute",
|
|
5390
|
+
right: "8px",
|
|
5391
|
+
top: "50%",
|
|
5392
|
+
transform: "translateY(-50%)",
|
|
5393
|
+
display: "flex",
|
|
5394
|
+
alignItems: "center",
|
|
5395
|
+
gap: "4px"
|
|
5396
|
+
},
|
|
5397
|
+
children: [
|
|
5398
|
+
urlInput && /* @__PURE__ */ jsx(
|
|
5399
|
+
Button,
|
|
5400
|
+
{
|
|
5401
|
+
text: "Clear",
|
|
5402
|
+
mode: "bleed",
|
|
5403
|
+
tone: "critical",
|
|
5404
|
+
onClick: () => {
|
|
5405
|
+
setUrlInput(""), validateUrl("");
|
|
5406
|
+
},
|
|
5407
|
+
disabled: isValidating,
|
|
5408
|
+
style: { fontSize: "11px", height: "24px" }
|
|
5409
|
+
}
|
|
5410
|
+
),
|
|
5411
|
+
isValidating && /* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: "Validating..." }),
|
|
5412
|
+
isValid === !0 && !isValidating && /* @__PURE__ */ jsx(CheckmarkCircleIcon, { style: { color: "#4caf50", fontSize: "18px" } }),
|
|
5413
|
+
isValid === !1 && !isValidating && /* @__PURE__ */ jsx(ErrorOutlineIcon, { style: { color: "#e74c3c", fontSize: "18px" } })
|
|
5414
|
+
]
|
|
5415
|
+
}
|
|
5416
|
+
)
|
|
5417
|
+
] }),
|
|
5418
|
+
urlError && /* @__PURE__ */ jsx(Card, { padding: 2, tone: "critical", radius: 2, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
5419
|
+
/* @__PURE__ */ jsx(ErrorOutlineIcon, { style: { color: "#e74c3c", flexShrink: 0 } }),
|
|
5420
|
+
/* @__PURE__ */ jsx(Text, { size: 0, style: { color: "#e74c3c" }, children: urlError })
|
|
5421
|
+
] }) })
|
|
5422
|
+
] }),
|
|
5423
|
+
watermark.imageUrl && /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
5424
|
+
/* @__PURE__ */ jsx(Card, { padding: 3, tone: "transparent", border: !0, radius: 2, children: /* @__PURE__ */ jsxs(
|
|
5425
|
+
Flex,
|
|
5426
|
+
{
|
|
5427
|
+
align: "center",
|
|
5428
|
+
justify: "space-between",
|
|
5429
|
+
gap: 3,
|
|
5430
|
+
style: { flexWrap: "wrap", alignItems: "flex-start" },
|
|
5431
|
+
children: [
|
|
5432
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, style: { minWidth: 240, flex: 1 }, children: [
|
|
5433
|
+
/* @__PURE__ */ jsx(Text, { size: 1, weight: "medium", children: "Positioning mode" }),
|
|
5434
|
+
/* @__PURE__ */ jsxs(Text, { size: 0, muted: !0, children: [
|
|
5435
|
+
"Choose between dragging on the canvas or manually editing the Mux",
|
|
5436
|
+
" ",
|
|
5437
|
+
/* @__PURE__ */ jsx("code", { children: "overlay_settings" }),
|
|
5438
|
+
" fields (as in",
|
|
5439
|
+
" ",
|
|
5440
|
+
/* @__PURE__ */ jsx(
|
|
5441
|
+
"a",
|
|
5442
|
+
{
|
|
5443
|
+
href: "https://www.mux.com/docs/guides/add-watermarks-to-your-videos",
|
|
5444
|
+
target: "_blank",
|
|
5445
|
+
rel: "noopener noreferrer",
|
|
5446
|
+
children: "the docs"
|
|
5447
|
+
}
|
|
5448
|
+
),
|
|
5449
|
+
")."
|
|
5450
|
+
] })
|
|
5451
|
+
] }),
|
|
5452
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, style: { flexWrap: "wrap" }, children: [
|
|
5453
|
+
/* @__PURE__ */ jsx(
|
|
5454
|
+
Button,
|
|
5455
|
+
{
|
|
5456
|
+
text: "Canvas",
|
|
5457
|
+
mode: mode === "canvas" ? "default" : "ghost",
|
|
5458
|
+
onClick: () => {
|
|
5459
|
+
setMode("canvas"), onChange({ ...watermark, enabled: !0, overlay_settings: void 0 });
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
),
|
|
5463
|
+
/* @__PURE__ */ jsx(
|
|
5464
|
+
Button,
|
|
5465
|
+
{
|
|
5466
|
+
text: "Manual",
|
|
5467
|
+
mode: mode === "manual" ? "default" : "ghost",
|
|
5468
|
+
onClick: () => {
|
|
5469
|
+
setMode("manual");
|
|
5470
|
+
const overlay = convertWatermarkToMuxOverlay({ ...watermark, enabled: !0 });
|
|
5471
|
+
updateOverlaySettings(overlay ?? {});
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5474
|
+
)
|
|
5475
|
+
] })
|
|
5476
|
+
]
|
|
5477
|
+
}
|
|
5478
|
+
) }),
|
|
5479
|
+
mode === "manual" && /* @__PURE__ */ jsx(Card, { padding: 3, tone: "transparent", border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
5480
|
+
/* @__PURE__ */ jsx(Text, { size: 1, weight: "medium", children: "Mux overlay_settings" }),
|
|
5481
|
+
/* @__PURE__ */ jsxs(Grid, { columns: [1, 2], gap: 3, style: { width: "100%" }, children: [
|
|
5482
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5483
|
+
/* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: "horizontal_align" }),
|
|
5484
|
+
/* @__PURE__ */ jsxs(
|
|
5485
|
+
"select",
|
|
5486
|
+
{
|
|
5487
|
+
value: watermark.overlay_settings?.horizontal_align || "right",
|
|
5488
|
+
onChange: (e) => updateOverlaySettings({
|
|
5489
|
+
horizontal_align: e.target.value || "right"
|
|
5490
|
+
}),
|
|
5491
|
+
style: {
|
|
5492
|
+
width: "100%",
|
|
5493
|
+
padding: "8px 10px",
|
|
5494
|
+
border: "1px solid #ccc",
|
|
5495
|
+
borderRadius: 4
|
|
5496
|
+
},
|
|
5497
|
+
children: [
|
|
5498
|
+
/* @__PURE__ */ jsx("option", { value: "left", children: "left" }),
|
|
5499
|
+
/* @__PURE__ */ jsx("option", { value: "center", children: "center" }),
|
|
5500
|
+
/* @__PURE__ */ jsx("option", { value: "right", children: "right" })
|
|
5501
|
+
]
|
|
5502
|
+
}
|
|
5503
|
+
)
|
|
5504
|
+
] }),
|
|
5505
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5506
|
+
/* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: "horizontal_margin (e.g. 2% or 40px)" }),
|
|
5507
|
+
/* @__PURE__ */ jsx(
|
|
5508
|
+
TextInput,
|
|
5509
|
+
{
|
|
5510
|
+
value: watermark.overlay_settings?.horizontal_margin || "2%",
|
|
5511
|
+
onChange: (e) => updateOverlaySettings({ horizontal_margin: e.currentTarget.value })
|
|
5512
|
+
}
|
|
5513
|
+
)
|
|
5514
|
+
] }),
|
|
5515
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5516
|
+
/* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: "vertical_align" }),
|
|
5517
|
+
/* @__PURE__ */ jsxs(
|
|
5518
|
+
"select",
|
|
5519
|
+
{
|
|
5520
|
+
value: watermark.overlay_settings?.vertical_align || "bottom",
|
|
5521
|
+
onChange: (e) => updateOverlaySettings({
|
|
5522
|
+
vertical_align: e.target.value || "bottom"
|
|
5523
|
+
}),
|
|
5524
|
+
style: {
|
|
5525
|
+
width: "100%",
|
|
5526
|
+
padding: "8px 10px",
|
|
5527
|
+
border: "1px solid #ccc",
|
|
5528
|
+
borderRadius: 4
|
|
5529
|
+
},
|
|
5530
|
+
children: [
|
|
5531
|
+
/* @__PURE__ */ jsx("option", { value: "top", children: "top" }),
|
|
5532
|
+
/* @__PURE__ */ jsx("option", { value: "middle", children: "middle" }),
|
|
5533
|
+
/* @__PURE__ */ jsx("option", { value: "bottom", children: "bottom" })
|
|
5534
|
+
]
|
|
5535
|
+
}
|
|
5536
|
+
)
|
|
5537
|
+
] }),
|
|
5538
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5539
|
+
/* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: "vertical_margin (e.g. 2% or 40px)" }),
|
|
5540
|
+
/* @__PURE__ */ jsx(
|
|
5541
|
+
TextInput,
|
|
5542
|
+
{
|
|
5543
|
+
value: watermark.overlay_settings?.vertical_margin || "2%",
|
|
5544
|
+
onChange: (e) => updateOverlaySettings({ vertical_margin: e.currentTarget.value })
|
|
5545
|
+
}
|
|
5546
|
+
)
|
|
5547
|
+
] }),
|
|
5548
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5549
|
+
/* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: "width (e.g. 25% or 80px)" }),
|
|
5550
|
+
/* @__PURE__ */ jsx(
|
|
5551
|
+
TextInput,
|
|
5552
|
+
{
|
|
5553
|
+
value: watermark.overlay_settings?.width || `${watermark.size ?? 20}%`,
|
|
5554
|
+
onChange: (e) => updateOverlaySettings({ width: e.currentTarget.value })
|
|
5555
|
+
}
|
|
5556
|
+
)
|
|
5557
|
+
] }),
|
|
5558
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5559
|
+
/* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: "opacity (e.g. 90%)" }),
|
|
5560
|
+
/* @__PURE__ */ jsx(
|
|
5561
|
+
TextInput,
|
|
5562
|
+
{
|
|
5563
|
+
value: watermark.overlay_settings?.opacity || `${Math.round((watermark.opacity ?? 0.7) * 100)}%`,
|
|
5564
|
+
onChange: (e) => updateOverlaySettings({ opacity: e.currentTarget.value })
|
|
5565
|
+
}
|
|
5566
|
+
)
|
|
5567
|
+
] })
|
|
5568
|
+
] }),
|
|
5569
|
+
/* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: "Margins and width accept either percentages or pixels, per the Mux guide." })
|
|
5570
|
+
] }) }),
|
|
5571
|
+
mode === "canvas" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5572
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
5573
|
+
/* @__PURE__ */ jsx(Text, { size: 1, weight: "medium", children: (() => {
|
|
5574
|
+
const sizePct = watermark.size || 20, contentW = getVideoContentBox().width;
|
|
5575
|
+
return contentW ? `Size: ${Math.max(1, Math.round(sizePct / 100 * contentW))}px` : `Size: ${sizePct}%`;
|
|
5576
|
+
})() }),
|
|
5577
|
+
/* @__PURE__ */ jsx(
|
|
5578
|
+
RangeInput,
|
|
5579
|
+
{
|
|
5580
|
+
type: "range",
|
|
5581
|
+
value: (() => {
|
|
5582
|
+
const sizePct = watermark.size || 20, contentW = getVideoContentBox().width;
|
|
5583
|
+
return contentW ? Math.max(1, Math.round(sizePct / 100 * contentW)) : sizePct;
|
|
5584
|
+
})(),
|
|
5585
|
+
min: (() => {
|
|
5586
|
+
const contentW = getVideoContentBox().width;
|
|
5587
|
+
return contentW ? Math.max(1, Math.round(contentW * 0.05)) : 5;
|
|
5588
|
+
})(),
|
|
5589
|
+
max: (() => {
|
|
5590
|
+
const contentW = getVideoContentBox().width;
|
|
5591
|
+
return contentW ? Math.max(1, Math.round(contentW * 0.5)) : 50;
|
|
5592
|
+
})(),
|
|
5593
|
+
step: 1,
|
|
5594
|
+
onChange: (e) => {
|
|
5595
|
+
const raw = Number(e.target.value), contentW = getVideoContentBox().width, nextPct = contentW ? raw / contentW * 100 : raw, clampedPct = Math.max(5, Math.min(50, nextPct));
|
|
5596
|
+
onChange({
|
|
5597
|
+
...watermark,
|
|
5598
|
+
size: clampedPct
|
|
5599
|
+
});
|
|
5600
|
+
}
|
|
5601
|
+
}
|
|
5602
|
+
)
|
|
5603
|
+
] }),
|
|
5604
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
5605
|
+
/* @__PURE__ */ jsxs(Text, { size: 1, weight: "medium", children: [
|
|
5606
|
+
"Opacity: ",
|
|
5607
|
+
Math.round((watermark.opacity ?? 0.7) * 100),
|
|
5608
|
+
"%"
|
|
5609
|
+
] }),
|
|
5610
|
+
/* @__PURE__ */ jsx(
|
|
5611
|
+
RangeInput,
|
|
5612
|
+
{
|
|
5613
|
+
type: "range",
|
|
5614
|
+
value: watermark.opacity ?? 0.7,
|
|
5615
|
+
min: 0,
|
|
5616
|
+
max: 1,
|
|
5617
|
+
step: 0.05,
|
|
5618
|
+
onChange: (e) => onChange({
|
|
5619
|
+
...watermark,
|
|
5620
|
+
opacity: Number(e.target.value)
|
|
5621
|
+
})
|
|
5622
|
+
}
|
|
5623
|
+
)
|
|
5624
|
+
] })
|
|
5625
|
+
] }),
|
|
5626
|
+
/* @__PURE__ */ jsx(Card, { padding: 2, tone: "transparent", border: !0, radius: 2, children: /* @__PURE__ */ jsx(Text, { size: 0, muted: !0, children: mode === "manual" ? "Manual mode: edit the overlay_settings fields above" : "\u{1F4A1} Drag the watermark on the preview to position it" }) })
|
|
5627
|
+
] })
|
|
5628
|
+
] });
|
|
5629
|
+
}
|
|
4832
5630
|
const ALL_LANGUAGE_CODES = LanguagesList.getAllCodes().map((code) => ({
|
|
4833
5631
|
value: code,
|
|
4834
5632
|
label: LanguagesList.getNativeName(code)
|
|
@@ -5270,7 +6068,7 @@ function UploadConfiguration({
|
|
|
5270
6068
|
startUpload,
|
|
5271
6069
|
onClose
|
|
5272
6070
|
}) {
|
|
5273
|
-
const id = useId(), autoTextTracks = useRef(
|
|
6071
|
+
const id = useId(), [watermarkValidationError, setWatermarkValidationError] = useState(null), watermarkPreviewContainerRef = useRef(null), watermarkPreviewVideoRef = useRef(null), autoTextTracks = useRef(
|
|
5274
6072
|
pluginConfig.video_quality === "plus" && pluginConfig.defaultAutogeneratedSubtitleLang ? [
|
|
5275
6073
|
{
|
|
5276
6074
|
_id: uuid(),
|
|
@@ -5306,6 +6104,8 @@ function UploadConfiguration({
|
|
|
5306
6104
|
return Object.assign({}, prev, { [action.action]: action.value });
|
|
5307
6105
|
case "drm_policy":
|
|
5308
6106
|
return Object.assign({}, prev, { [action.action]: action.value });
|
|
6107
|
+
case "watermark":
|
|
6108
|
+
return Object.assign({}, prev, { watermark: action.value });
|
|
5309
6109
|
// Updating individual tracks
|
|
5310
6110
|
case "track": {
|
|
5311
6111
|
const text_tracks = [...prev.text_tracks], target_track_i = text_tracks.findIndex(({ _id: _id2 }) => _id2 === action.id);
|
|
@@ -5373,7 +6173,12 @@ function UploadConfiguration({
|
|
|
5373
6173
|
]);
|
|
5374
6174
|
const { disableTextTrackConfig, disableUploadConfig } = pluginConfig, skipConfig = disableTextTrackConfig && disableUploadConfig;
|
|
5375
6175
|
if (useEffect(() => {
|
|
5376
|
-
|
|
6176
|
+
if (skipConfig) {
|
|
6177
|
+
const { settings, watermark } = formatUploadConfig(config, secrets, {
|
|
6178
|
+
videoAspectRatio: videoAssetMetadata?.aspectRatio
|
|
6179
|
+
});
|
|
6180
|
+
startUpload(settings, watermark);
|
|
6181
|
+
}
|
|
5377
6182
|
}, []), skipConfig) return null;
|
|
5378
6183
|
const basicConfig = config.video_quality !== "plus" && config.video_quality !== "premium", playbackPolicySelected = config.public_policy || config.signed_policy || config.drm_policy, maxSupportedResolution = RESOLUTION_TIERS.findIndex(
|
|
5379
6184
|
(rt) => rt.value === pluginConfig.max_resolution_tier
|
|
@@ -5389,11 +6194,11 @@ function UploadConfiguration({
|
|
|
5389
6194
|
header: "Configure Mux Upload",
|
|
5390
6195
|
onClose,
|
|
5391
6196
|
children: /* @__PURE__ */ jsxs(Stack, { padding: 4, space: 2, children: [
|
|
5392
|
-
validationError && /* @__PURE__ */ jsx(Card, { padding: 3, tone: "critical", radius: 2, marginBottom: 2, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "flex-start", children: [
|
|
6197
|
+
(validationError || watermarkValidationError) && /* @__PURE__ */ jsx(Card, { padding: 3, tone: "critical", radius: 2, marginBottom: 2, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "flex-start", children: [
|
|
5393
6198
|
/* @__PURE__ */ jsx(ErrorOutlineIcon, { width: 20, height: 20 }),
|
|
5394
6199
|
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
5395
6200
|
/* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "Validation Error" }),
|
|
5396
|
-
/* @__PURE__ */ jsx(Text, { size: 1, children: validationError })
|
|
6201
|
+
/* @__PURE__ */ jsx(Text, { size: 1, children: validationError || watermarkValidationError })
|
|
5397
6202
|
] })
|
|
5398
6203
|
] }) }),
|
|
5399
6204
|
/* @__PURE__ */ jsx(Label$1, { size: 3, children: "FILE TO UPLOAD" }),
|
|
@@ -5460,27 +6265,41 @@ function UploadConfiguration({
|
|
|
5460
6265
|
}) })
|
|
5461
6266
|
}
|
|
5462
6267
|
),
|
|
5463
|
-
!basicConfig && /* @__PURE__ */
|
|
5464
|
-
/* @__PURE__ */ jsx(
|
|
5465
|
-
|
|
5466
|
-
|
|
6268
|
+
!basicConfig && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6269
|
+
/* @__PURE__ */ jsx(FormField$2, { title: "Additional Configuration", children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
6270
|
+
/* @__PURE__ */ jsx(PlaybackPolicy, { id, config, secrets, dispatch }),
|
|
6271
|
+
maxSupportedResolution > 0 && /* @__PURE__ */ jsx(
|
|
6272
|
+
ResolutionTierSelector,
|
|
6273
|
+
{
|
|
6274
|
+
id,
|
|
6275
|
+
config,
|
|
6276
|
+
dispatch,
|
|
6277
|
+
maxSupportedResolution
|
|
6278
|
+
}
|
|
6279
|
+
),
|
|
6280
|
+
/* @__PURE__ */ jsx(StaticRenditionSelector, { id, config, dispatch }),
|
|
6281
|
+
!disableTextTrackConfig && /* @__PURE__ */ jsx(
|
|
6282
|
+
TextTracksEditor,
|
|
6283
|
+
{
|
|
6284
|
+
tracks: config.text_tracks,
|
|
6285
|
+
dispatch,
|
|
6286
|
+
defaultLang: pluginConfig.defaultAutogeneratedSubtitleLang
|
|
6287
|
+
}
|
|
6288
|
+
)
|
|
6289
|
+
] }) }),
|
|
6290
|
+
/* @__PURE__ */ jsx(
|
|
6291
|
+
WatermarkSection,
|
|
5467
6292
|
{
|
|
5468
|
-
id,
|
|
5469
6293
|
config,
|
|
5470
6294
|
dispatch,
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
TextTracksEditor,
|
|
5477
|
-
{
|
|
5478
|
-
tracks: config.text_tracks,
|
|
5479
|
-
dispatch,
|
|
5480
|
-
defaultLang: pluginConfig.defaultAutogeneratedSubtitleLang
|
|
6295
|
+
stagedUpload,
|
|
6296
|
+
videoAssetMetadata,
|
|
6297
|
+
watermarkPreviewContainerRef,
|
|
6298
|
+
watermarkPreviewVideoRef,
|
|
6299
|
+
onValidationChange: setWatermarkValidationError
|
|
5481
6300
|
}
|
|
5482
6301
|
)
|
|
5483
|
-
] })
|
|
6302
|
+
] })
|
|
5484
6303
|
] }),
|
|
5485
6304
|
/* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsx(
|
|
5486
6305
|
Button,
|
|
@@ -5490,7 +6309,12 @@ function UploadConfiguration({
|
|
|
5490
6309
|
text: "Upload",
|
|
5491
6310
|
tone: "positive",
|
|
5492
6311
|
onClick: () => {
|
|
5493
|
-
|
|
6312
|
+
if (!validationError) {
|
|
6313
|
+
const { settings, watermark } = formatUploadConfig(config, secrets, {
|
|
6314
|
+
videoAspectRatio: videoAssetMetadata?.aspectRatio
|
|
6315
|
+
});
|
|
6316
|
+
startUpload(settings, watermark);
|
|
6317
|
+
}
|
|
5494
6318
|
}
|
|
5495
6319
|
}
|
|
5496
6320
|
) })
|
|
@@ -5505,36 +6329,178 @@ function setAdvancedPlaybackPolicy(config, secrets) {
|
|
|
5505
6329
|
drm_configuration_id: secrets.drmConfigId ?? void 0
|
|
5506
6330
|
}) : console.error("Selected DRM Policy but missing DRM Configuration Id")), advanced_playback_policies;
|
|
5507
6331
|
}
|
|
5508
|
-
function formatUploadConfig(config, secrets) {
|
|
6332
|
+
function formatUploadConfig(config, secrets, options) {
|
|
5509
6333
|
const generated_subtitles = config.text_tracks.filter(isAutogeneratedTrack).map((track) => ({
|
|
5510
6334
|
name: track.name,
|
|
5511
6335
|
language_code: track.language_code
|
|
5512
|
-
}))
|
|
6336
|
+
})), inputs = [
|
|
6337
|
+
{
|
|
6338
|
+
type: "video",
|
|
6339
|
+
generated_subtitles: generated_subtitles.length > 0 ? generated_subtitles : void 0
|
|
6340
|
+
},
|
|
6341
|
+
...config.text_tracks.filter(isCustomTextTrack).reduce(
|
|
6342
|
+
(acc, track) => (track.language_code && track.file && track.name && acc.push({
|
|
6343
|
+
url: track.file.contents,
|
|
6344
|
+
type: "text",
|
|
6345
|
+
text_type: track.type === "subtitles" ? "subtitles" : void 0,
|
|
6346
|
+
language_code: track.language_code,
|
|
6347
|
+
name: track.name,
|
|
6348
|
+
closed_captions: track.type === "captions"
|
|
6349
|
+
}), acc),
|
|
6350
|
+
[]
|
|
6351
|
+
)
|
|
6352
|
+
];
|
|
6353
|
+
if (config.watermark?.imageUrl) {
|
|
6354
|
+
const watermarkForMux = { ...config.watermark, enabled: !0 }, overlaySettings = convertWatermarkToMuxOverlay(watermarkForMux, {
|
|
6355
|
+
videoAspectRatio: options?.videoAspectRatio ?? void 0,
|
|
6356
|
+
units: "px"
|
|
6357
|
+
});
|
|
6358
|
+
overlaySettings && inputs.push({
|
|
6359
|
+
url: config.watermark.imageUrl,
|
|
6360
|
+
overlay_settings: overlaySettings
|
|
6361
|
+
});
|
|
6362
|
+
}
|
|
5513
6363
|
return {
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
text_type: track.type === "subtitles" ? "subtitles" : void 0,
|
|
5524
|
-
language_code: track.language_code,
|
|
5525
|
-
name: track.name,
|
|
5526
|
-
closed_captions: track.type === "captions"
|
|
5527
|
-
}), acc),
|
|
5528
|
-
[]
|
|
5529
|
-
)
|
|
5530
|
-
],
|
|
5531
|
-
static_renditions: config.static_renditions.length > 0 ? config.static_renditions.map((resolution) => ({ resolution })) : void 0,
|
|
5532
|
-
advanced_playback_policies: setAdvancedPlaybackPolicy(config, secrets),
|
|
5533
|
-
max_resolution_tier: config.max_resolution_tier,
|
|
5534
|
-
video_quality: config.video_quality,
|
|
5535
|
-
normalize_audio: config.normalize_audio
|
|
6364
|
+
settings: {
|
|
6365
|
+
input: inputs,
|
|
6366
|
+
static_renditions: config.static_renditions.length > 0 ? config.static_renditions.map((resolution) => ({ resolution })) : void 0,
|
|
6367
|
+
advanced_playback_policies: setAdvancedPlaybackPolicy(config, secrets),
|
|
6368
|
+
max_resolution_tier: config.max_resolution_tier,
|
|
6369
|
+
video_quality: config.video_quality,
|
|
6370
|
+
normalize_audio: config.normalize_audio
|
|
6371
|
+
},
|
|
6372
|
+
watermark: config.watermark?.imageUrl ? { ...config.watermark, enabled: !0 } : void 0
|
|
5536
6373
|
};
|
|
5537
6374
|
}
|
|
6375
|
+
function WatermarkSection({
|
|
6376
|
+
config,
|
|
6377
|
+
dispatch,
|
|
6378
|
+
stagedUpload,
|
|
6379
|
+
videoAssetMetadata,
|
|
6380
|
+
watermarkPreviewContainerRef,
|
|
6381
|
+
watermarkPreviewVideoRef,
|
|
6382
|
+
onValidationChange
|
|
6383
|
+
}) {
|
|
6384
|
+
return videoAssetMetadata?.isAudioOnly !== !1 ? null : /* @__PURE__ */ jsx(
|
|
6385
|
+
FormField$2,
|
|
6386
|
+
{
|
|
6387
|
+
title: "Watermark",
|
|
6388
|
+
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6389
|
+
"Add a watermark overlay to your video using Mux's native watermark support.",
|
|
6390
|
+
" ",
|
|
6391
|
+
/* @__PURE__ */ jsx(
|
|
6392
|
+
"a",
|
|
6393
|
+
{
|
|
6394
|
+
href: "https://www.mux.com/docs/guides/add-watermarks-to-your-videos",
|
|
6395
|
+
target: "_blank",
|
|
6396
|
+
rel: "noopener noreferrer",
|
|
6397
|
+
children: "Learn more about Mux watermarks."
|
|
6398
|
+
}
|
|
6399
|
+
)
|
|
6400
|
+
] }),
|
|
6401
|
+
children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
6402
|
+
/* @__PURE__ */ jsx(
|
|
6403
|
+
WatermarkControls,
|
|
6404
|
+
{
|
|
6405
|
+
watermark: config.watermark || { enabled: !1 },
|
|
6406
|
+
onChange: (watermark) => {
|
|
6407
|
+
dispatch({ action: "watermark", value: watermark });
|
|
6408
|
+
},
|
|
6409
|
+
onValidationChange,
|
|
6410
|
+
previewContainerRef: watermarkPreviewContainerRef,
|
|
6411
|
+
previewVideoRef: watermarkPreviewVideoRef
|
|
6412
|
+
}
|
|
6413
|
+
),
|
|
6414
|
+
config.watermark?.imageUrl && stagedUpload.type === "file" && // Canvas preview is only shown in "Canvas" mode (no explicit overlay_settings)
|
|
6415
|
+
!config.watermark.overlay_settings && /* @__PURE__ */ jsx(
|
|
6416
|
+
WatermarkPreview,
|
|
6417
|
+
{
|
|
6418
|
+
stagedUpload,
|
|
6419
|
+
watermark: config.watermark,
|
|
6420
|
+
videoAspectRatio: videoAssetMetadata.aspectRatio,
|
|
6421
|
+
onWatermarkChange: (watermark) => {
|
|
6422
|
+
dispatch({ action: "watermark", value: watermark });
|
|
6423
|
+
},
|
|
6424
|
+
previewContainerRef: watermarkPreviewContainerRef,
|
|
6425
|
+
videoRef: watermarkPreviewVideoRef
|
|
6426
|
+
}
|
|
6427
|
+
)
|
|
6428
|
+
] })
|
|
6429
|
+
}
|
|
6430
|
+
);
|
|
6431
|
+
}
|
|
6432
|
+
const WatermarkPreview = memo(function({
|
|
6433
|
+
stagedUpload,
|
|
6434
|
+
watermark,
|
|
6435
|
+
onWatermarkChange,
|
|
6436
|
+
videoAspectRatio,
|
|
6437
|
+
previewContainerRef,
|
|
6438
|
+
videoRef
|
|
6439
|
+
}) {
|
|
6440
|
+
useEffect(() => {
|
|
6441
|
+
if (videoRef.current && stagedUpload.type === "file") {
|
|
6442
|
+
const file = stagedUpload.files[0], url = URL.createObjectURL(file);
|
|
6443
|
+
return videoRef.current.src = url, () => {
|
|
6444
|
+
URL.revokeObjectURL(url);
|
|
6445
|
+
};
|
|
6446
|
+
}
|
|
6447
|
+
}, [stagedUpload, videoRef]);
|
|
6448
|
+
const isVertical = videoAspectRatio != null && videoAspectRatio < 1;
|
|
6449
|
+
return /* @__PURE__ */ jsx(
|
|
6450
|
+
Card,
|
|
6451
|
+
{
|
|
6452
|
+
tone: "transparent",
|
|
6453
|
+
border: !0,
|
|
6454
|
+
style: {
|
|
6455
|
+
overflow: "hidden",
|
|
6456
|
+
// For vertical videos, center the preview and limit its width
|
|
6457
|
+
display: "flex",
|
|
6458
|
+
justifyContent: "center"
|
|
6459
|
+
},
|
|
6460
|
+
children: /* @__PURE__ */ jsxs(
|
|
6461
|
+
"div",
|
|
6462
|
+
{
|
|
6463
|
+
ref: previewContainerRef,
|
|
6464
|
+
style: {
|
|
6465
|
+
position: "relative",
|
|
6466
|
+
// For vertical videos: limit width so the preview doesn't get too tall
|
|
6467
|
+
// For horizontal videos: use full width
|
|
6468
|
+
width: isVertical ? "auto" : "100%",
|
|
6469
|
+
aspectRatio: videoAspectRatio ? String(videoAspectRatio) : "16/9",
|
|
6470
|
+
...isVertical ? { height: "400px", maxHeight: "50vh" } : { minHeight: "200px" },
|
|
6471
|
+
overflow: "hidden"
|
|
6472
|
+
},
|
|
6473
|
+
children: [
|
|
6474
|
+
/* @__PURE__ */ jsx(
|
|
6475
|
+
"video",
|
|
6476
|
+
{
|
|
6477
|
+
ref: videoRef,
|
|
6478
|
+
style: {
|
|
6479
|
+
position: "absolute",
|
|
6480
|
+
top: 0,
|
|
6481
|
+
left: 0,
|
|
6482
|
+
width: "100%",
|
|
6483
|
+
height: "100%",
|
|
6484
|
+
objectFit: "fill",
|
|
6485
|
+
display: "block"
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
),
|
|
6489
|
+
/* @__PURE__ */ jsx(
|
|
6490
|
+
DraggableWatermark,
|
|
6491
|
+
{
|
|
6492
|
+
watermark,
|
|
6493
|
+
onChange: onWatermarkChange,
|
|
6494
|
+
containerRef: previewContainerRef,
|
|
6495
|
+
videoElementRef: videoRef
|
|
6496
|
+
}
|
|
6497
|
+
)
|
|
6498
|
+
]
|
|
6499
|
+
}
|
|
6500
|
+
)
|
|
6501
|
+
}
|
|
6502
|
+
);
|
|
6503
|
+
});
|
|
5538
6504
|
function withFocusRing(component) {
|
|
5539
6505
|
return styled(component)((props) => {
|
|
5540
6506
|
const border = {
|
|
@@ -5773,7 +6739,7 @@ function Uploader(props) {
|
|
|
5773
6739
|
window.removeEventListener("beforeunload", handleBeforeUnload), window.removeEventListener("pagehide", handleBeforeUnload), cleanup();
|
|
5774
6740
|
};
|
|
5775
6741
|
}, [props.client, props.asset?._id]);
|
|
5776
|
-
const startUpload = (settings) => {
|
|
6742
|
+
const startUpload = (settings, watermark) => {
|
|
5777
6743
|
const { stagedUpload } = state;
|
|
5778
6744
|
if (!stagedUpload || uploadRef.current) return;
|
|
5779
6745
|
dispatch({ action: "commitUpload" });
|
|
@@ -5783,14 +6749,16 @@ function Uploader(props) {
|
|
|
5783
6749
|
uploadObservable = uploadUrl({
|
|
5784
6750
|
client: props.client,
|
|
5785
6751
|
url: stagedUpload.url,
|
|
5786
|
-
settings
|
|
6752
|
+
settings,
|
|
6753
|
+
watermark
|
|
5787
6754
|
});
|
|
5788
6755
|
break;
|
|
5789
6756
|
case "file":
|
|
5790
6757
|
uploadObservable = uploadFile({
|
|
5791
6758
|
client: props.client,
|
|
5792
6759
|
file: stagedUpload.files[0],
|
|
5793
|
-
settings
|
|
6760
|
+
settings,
|
|
6761
|
+
watermark
|
|
5794
6762
|
}).pipe(
|
|
5795
6763
|
takeUntil(
|
|
5796
6764
|
cancelUploadButton.observable.pipe(
|
|
@@ -6051,7 +7019,12 @@ const muxVideoSchema = {
|
|
|
6051
7019
|
{ type: "number", name: "max_width" },
|
|
6052
7020
|
{ type: "number", name: "max_frame_rate" },
|
|
6053
7021
|
{ type: "number", name: "duration" },
|
|
6054
|
-
{ type: "number", name: "max_height" }
|
|
7022
|
+
{ type: "number", name: "max_height" },
|
|
7023
|
+
{ type: "string", name: "language_code" },
|
|
7024
|
+
{ type: "string", name: "name" },
|
|
7025
|
+
{ type: "string", name: "status" },
|
|
7026
|
+
{ type: "string", name: "text_source" },
|
|
7027
|
+
{ type: "string", name: "text_type" }
|
|
6055
7028
|
]
|
|
6056
7029
|
}, muxPlaybackId = {
|
|
6057
7030
|
name: "mux.playbackId",
|