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