sanity-plugin-mux-input 2.16.0 → 2.18.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 +916 -78
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +916 -78
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/actions/upload.ts +47 -7
- package/src/components/AddCaptionDialog.tsx +28 -9
- package/src/components/DraggableWatermark.tsx +877 -0
- package/src/components/EditCaptionDialog.tsx +4 -2
- package/src/components/UploadConfiguration.tsx +259 -59
- package/src/components/Uploader.tsx +7 -1
- package/src/components/VideoPlayer.tsx +2 -0
- package/src/hooks/useMediaMetadata.ts +3 -0
- package/src/util/convertWatermarkToMux.ts +160 -0
- package/src/util/formatDriveShareLink.ts +64 -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}`,
|
|
@@ -1888,7 +1888,15 @@ function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
|
1888
1888
|
null
|
|
1889
1889
|
), [name2, setName] = React.useState(""), [isSubmitting, setIsSubmitting] = React.useState(!1), [selectedFile, setSelectedFile] = React.useState(null), fileInputRef = React.useRef(null), uploadVttFile = async (file) => (await client.assets.upload("file", file, {
|
|
1890
1890
|
filename: file.name
|
|
1891
|
-
})).url,
|
|
1891
|
+
})).url, refreshAssetData = async () => {
|
|
1892
|
+
if (!(!asset._id || !asset.assetId))
|
|
1893
|
+
try {
|
|
1894
|
+
const latestAssetData = await getAsset(client, asset.assetId), dataWithKeys = addKeysToMuxData(latestAssetData.data);
|
|
1895
|
+
await client.patch(asset._id).set({ data: dataWithKeys, status: latestAssetData.data.status }).commit({ returnDocuments: !1 });
|
|
1896
|
+
} catch (refreshError) {
|
|
1897
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
1898
|
+
}
|
|
1899
|
+
}, handleAddTrackFromUrl = async () => {
|
|
1892
1900
|
if (!asset.assetId)
|
|
1893
1901
|
throw new Error("Asset ID is required");
|
|
1894
1902
|
const trimmedName = name2.trim(), trimmedLanguageCode = languageCode.trim();
|
|
@@ -1898,9 +1906,9 @@ function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
|
1898
1906
|
vttUrlToUse = await uploadVttFile(selectedFile);
|
|
1899
1907
|
} catch (uploadError) {
|
|
1900
1908
|
throw toast.push({
|
|
1901
|
-
title: "Failed to upload
|
|
1909
|
+
title: "Failed to upload caption file",
|
|
1902
1910
|
status: "error",
|
|
1903
|
-
description: "Could not upload the
|
|
1911
|
+
description: "Could not upload the caption file to Sanity. Please try again."
|
|
1904
1912
|
}), setIsSubmitting(!1), uploadError;
|
|
1905
1913
|
}
|
|
1906
1914
|
await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
|
|
@@ -1913,13 +1921,13 @@ function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
|
1913
1921
|
assetId: asset.assetId,
|
|
1914
1922
|
trackName: trimmedName,
|
|
1915
1923
|
trackLanguageCode: trimmedLanguageCode,
|
|
1916
|
-
onTrackErrored: (track) => {
|
|
1924
|
+
onTrackErrored: async (track) => {
|
|
1917
1925
|
const errorMessage = track.error?.messages?.[0] || track.error?.type || "The track failed to download from the provided URL";
|
|
1918
1926
|
toast.push({
|
|
1919
1927
|
title: "Caption track failed",
|
|
1920
1928
|
status: "error",
|
|
1921
1929
|
description: errorMessage
|
|
1922
|
-
}), onAdd(track), onClose();
|
|
1930
|
+
}), await refreshAssetData(), onAdd(track), onClose();
|
|
1923
1931
|
}
|
|
1924
1932
|
});
|
|
1925
1933
|
if (!result.found || !result.track) {
|
|
@@ -1936,10 +1944,10 @@ function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
|
1936
1944
|
title: "Caption track is processing",
|
|
1937
1945
|
status: "info",
|
|
1938
1946
|
description: "The track was created and is being processed. It will appear in the list shortly."
|
|
1939
|
-
}), onAdd(result.track), onClose();
|
|
1947
|
+
}), await refreshAssetData(), onAdd(result.track), onClose();
|
|
1940
1948
|
return;
|
|
1941
1949
|
}
|
|
1942
|
-
toast.push({
|
|
1950
|
+
await refreshAssetData(), toast.push({
|
|
1943
1951
|
title: "Caption track added",
|
|
1944
1952
|
status: "success",
|
|
1945
1953
|
description: "Caption track added successfully"
|
|
@@ -1977,9 +1985,9 @@ function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
|
1977
1985
|
if (!isAutogenerated) {
|
|
1978
1986
|
if (!selectedFile && !vttUrl.trim()) {
|
|
1979
1987
|
toast.push({
|
|
1980
|
-
title: "
|
|
1988
|
+
title: "Caption file or URL required",
|
|
1981
1989
|
status: "error",
|
|
1982
|
-
description: "Please select a VTT file or enter a
|
|
1990
|
+
description: "Please select a VTT or SRT file or enter a caption file URL"
|
|
1983
1991
|
});
|
|
1984
1992
|
return;
|
|
1985
1993
|
}
|
|
@@ -1990,7 +1998,7 @@ function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
|
1990
1998
|
toast.push({
|
|
1991
1999
|
title: "Invalid URL",
|
|
1992
2000
|
status: "error",
|
|
1993
|
-
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt)"
|
|
2001
|
+
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt or subtitles.srt)"
|
|
1994
2002
|
});
|
|
1995
2003
|
return;
|
|
1996
2004
|
}
|
|
@@ -2072,7 +2080,7 @@ function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
|
2072
2080
|
{
|
|
2073
2081
|
ref: fileInputRef,
|
|
2074
2082
|
type: "file",
|
|
2075
|
-
accept: ".vtt,text/vtt",
|
|
2083
|
+
accept: ".vtt,text/vtt,.srt,application/x-subrip",
|
|
2076
2084
|
style: { display: "none" },
|
|
2077
2085
|
onChange: (e) => {
|
|
2078
2086
|
e.target.files && e.target.files.length > 0 && !isSubmitting && (setSelectedFile(e.target.files[0]), setVttUrl(""));
|
|
@@ -2080,9 +2088,9 @@ function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
|
2080
2088
|
}
|
|
2081
2089
|
)
|
|
2082
2090
|
] }),
|
|
2083
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, style: { textAlign: "center" }, children: "Or enter the
|
|
2091
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, style: { textAlign: "center" }, children: "Or enter the caption file URL" }),
|
|
2084
2092
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
2085
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "vtt-url", children: "
|
|
2093
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "vtt-url", children: "Caption File URL (.vtt or .srt)" }),
|
|
2086
2094
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2087
2095
|
ui.TextInput,
|
|
2088
2096
|
{
|
|
@@ -2215,8 +2223,8 @@ function EditCaptionDialog({ asset, track, onUpdate, onClose }) {
|
|
|
2215
2223
|
})).url, refreshAssetData = async () => {
|
|
2216
2224
|
if (!(!asset._id || !asset.assetId))
|
|
2217
2225
|
try {
|
|
2218
|
-
const latestAssetData = await getAsset(client, asset.assetId);
|
|
2219
|
-
await client.patch(asset._id).set({ data:
|
|
2226
|
+
const latestAssetData = await getAsset(client, asset.assetId), dataWithKeys = addKeysToMuxData(latestAssetData.data);
|
|
2227
|
+
await client.patch(asset._id).set({ data: dataWithKeys, status: latestAssetData.data.status }).commit({ returnDocuments: !1 });
|
|
2220
2228
|
} catch (refreshError) {
|
|
2221
2229
|
console.error("Failed to refresh asset data:", refreshError);
|
|
2222
2230
|
}
|
|
@@ -3060,7 +3068,7 @@ function VideoPlayer({
|
|
|
3060
3068
|
hlsConfig,
|
|
3061
3069
|
...props
|
|
3062
3070
|
}) {
|
|
3063
|
-
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = React.useRef(null), [error, setError] = React.useState(), playbackId = React.useMemo(() => {
|
|
3071
|
+
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = React.useRef(null), playerContainerRef = React.useRef(null), [error, setError] = React.useState(), playbackId = React.useMemo(() => {
|
|
3064
3072
|
try {
|
|
3065
3073
|
return getPlaybackId(asset, ["public", "signed", "drm"]);
|
|
3066
3074
|
} catch {
|
|
@@ -3118,6 +3126,7 @@ function VideoPlayer({
|
|
|
3118
3126
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3119
3127
|
ui.Card,
|
|
3120
3128
|
{
|
|
3129
|
+
ref: playerContainerRef,
|
|
3121
3130
|
tone: "transparent",
|
|
3122
3131
|
style: {
|
|
3123
3132
|
aspectRatio,
|
|
@@ -3154,7 +3163,7 @@ function VideoPlayer({
|
|
|
3154
3163
|
crossOrigin: "anonymous",
|
|
3155
3164
|
metadata: {
|
|
3156
3165
|
player_name: "Sanity Admin Dashboard",
|
|
3157
|
-
player_version: "2.
|
|
3166
|
+
player_version: "2.18.0",
|
|
3158
3167
|
page_type: "Preview Player"
|
|
3159
3168
|
},
|
|
3160
3169
|
audio: isAudio,
|
|
@@ -4316,6 +4325,56 @@ function createUpChunkObservable(uuid2, uploadUrl2, source) {
|
|
|
4316
4325
|
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();
|
|
4317
4326
|
});
|
|
4318
4327
|
}
|
|
4328
|
+
function formatDriveShareLink(url) {
|
|
4329
|
+
const formatExportLink = (id) => `https://drive.google.com/uc?export=download&id=${id}`;
|
|
4330
|
+
try {
|
|
4331
|
+
const trimmed = url.trim(), parsed = new URL(trimmed);
|
|
4332
|
+
if (parsed.hostname !== "drive.google.com")
|
|
4333
|
+
throw new Error("URL is not from Google Drive.");
|
|
4334
|
+
const id = parsed.searchParams.get("id") || "";
|
|
4335
|
+
if (id.length)
|
|
4336
|
+
return formatExportLink(id);
|
|
4337
|
+
const path2 = parsed.pathname.split("/") || [];
|
|
4338
|
+
if (path2.includes("file") && path2.includes("d")) {
|
|
4339
|
+
const index = path2.findIndex((value) => value === "d") + 1, fileId = path2.at(index) || "";
|
|
4340
|
+
return formatExportLink(fileId);
|
|
4341
|
+
}
|
|
4342
|
+
if (path2.includes("folders")) {
|
|
4343
|
+
const index = path2.findIndex((value) => value === "folders") + 1, folderId = path2.at(index) || "";
|
|
4344
|
+
return formatExportLink(folderId);
|
|
4345
|
+
}
|
|
4346
|
+
throw new Error("URL was not recognized.");
|
|
4347
|
+
} catch {
|
|
4348
|
+
return url;
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
function roundPxString(value) {
|
|
4352
|
+
if (typeof value != "string") return;
|
|
4353
|
+
const trimmed = value.trim();
|
|
4354
|
+
if (!trimmed.endsWith("px")) return;
|
|
4355
|
+
const n = Number(trimmed.slice(0, -2));
|
|
4356
|
+
if (!Number.isFinite(n)) return;
|
|
4357
|
+
let rounded = Math.round(n);
|
|
4358
|
+
return rounded === 0 && (rounded = n < 0 ? -1 : 1), `${rounded}px`;
|
|
4359
|
+
}
|
|
4360
|
+
function sanitizeOverlaySettingsInPlace(settings) {
|
|
4361
|
+
const inputs = settings.input;
|
|
4362
|
+
if (inputs)
|
|
4363
|
+
for (const input of inputs) {
|
|
4364
|
+
const overlay = input.overlay_settings;
|
|
4365
|
+
if (!overlay) continue;
|
|
4366
|
+
const hm = roundPxString(overlay.horizontal_margin), vm = roundPxString(overlay.vertical_margin), w = roundPxString(overlay.width);
|
|
4367
|
+
hm && (overlay.horizontal_margin = hm), vm && (overlay.vertical_margin = vm), w && (overlay.width = w);
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
function sanitizePxStringsInJson(json) {
|
|
4371
|
+
return json.replace(/"(-?\d+(?:\.\d+)?)px"/g, (_match, num) => {
|
|
4372
|
+
const n = Number(num);
|
|
4373
|
+
if (!Number.isFinite(n)) return _match;
|
|
4374
|
+
let rounded = Math.round(n);
|
|
4375
|
+
return rounded === 0 && (rounded = n < 0 ? -1 : 1), `"${rounded}px"`;
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4319
4378
|
function cancelUpload(client, uuid2) {
|
|
4320
4379
|
return client.observable.request({
|
|
4321
4380
|
url: `/addons/mux/uploads/${client.config().dataset}/${uuid2}`,
|
|
@@ -4326,7 +4385,8 @@ function cancelUpload(client, uuid2) {
|
|
|
4326
4385
|
function uploadUrl({
|
|
4327
4386
|
url,
|
|
4328
4387
|
settings,
|
|
4329
|
-
client
|
|
4388
|
+
client,
|
|
4389
|
+
watermark
|
|
4330
4390
|
}) {
|
|
4331
4391
|
return testUrl(url).pipe(
|
|
4332
4392
|
operators.switchMap((validUrl) => rxjs.concat(
|
|
@@ -4336,9 +4396,9 @@ function uploadUrl({
|
|
|
4336
4396
|
if (!json || !json.status)
|
|
4337
4397
|
return rxjs.throwError(new Error("Invalid credentials"));
|
|
4338
4398
|
const uuid$1 = uuid.uuid(), muxBody = settings;
|
|
4339
|
-
muxBody.input || (muxBody.input = [{ type: "video" }]), muxBody.input[0].url = validUrl;
|
|
4399
|
+
muxBody.input || (muxBody.input = [{ type: "video" }]), muxBody.input[0].url = validUrl, sanitizeOverlaySettingsInPlace(muxBody);
|
|
4340
4400
|
const query = {
|
|
4341
|
-
muxBody: JSON.stringify(muxBody),
|
|
4401
|
+
muxBody: sanitizePxStringsInJson(JSON.stringify(muxBody)),
|
|
4342
4402
|
filename: validUrl.split("/").slice(-1)[0]
|
|
4343
4403
|
}, dataset = client.config().dataset;
|
|
4344
4404
|
return rxjs.defer(
|
|
@@ -4366,7 +4426,8 @@ function uploadUrl({
|
|
|
4366
4426
|
function uploadFile({
|
|
4367
4427
|
settings,
|
|
4368
4428
|
client,
|
|
4369
|
-
file
|
|
4429
|
+
file,
|
|
4430
|
+
watermark
|
|
4370
4431
|
}) {
|
|
4371
4432
|
return testFile(file).pipe(
|
|
4372
4433
|
operators.switchMap((fileOptions) => rxjs.concat(
|
|
@@ -4376,7 +4437,7 @@ function uploadFile({
|
|
|
4376
4437
|
if (!json || !json.status)
|
|
4377
4438
|
return rxjs.throwError(() => new Error("Invalid credentials"));
|
|
4378
4439
|
const uuid$1 = uuid.uuid(), body = settings;
|
|
4379
|
-
return rxjs.concat(
|
|
4440
|
+
return sanitizeOverlaySettingsInPlace(body), rxjs.concat(
|
|
4380
4441
|
rxjs.of({ type: "uuid", uuid: uuid$1 }),
|
|
4381
4442
|
rxjs.defer(
|
|
4382
4443
|
() => client.observable.request({
|
|
@@ -4430,7 +4491,7 @@ function pollUpload(client, uuid2) {
|
|
|
4430
4491
|
}, 2e3);
|
|
4431
4492
|
});
|
|
4432
4493
|
}
|
|
4433
|
-
async function updateAssetDocumentFromUpload(client, uuid2) {
|
|
4494
|
+
async function updateAssetDocumentFromUpload(client, uuid2, _watermark) {
|
|
4434
4495
|
let upload, asset;
|
|
4435
4496
|
try {
|
|
4436
4497
|
upload = await pollUpload(client, uuid2);
|
|
@@ -4464,14 +4525,15 @@ function testUrl(url) {
|
|
|
4464
4525
|
const error = new Error("Invalid URL");
|
|
4465
4526
|
if (typeof url != "string")
|
|
4466
4527
|
return rxjs.throwError(error);
|
|
4467
|
-
|
|
4528
|
+
let formattedUrl = url.trim();
|
|
4529
|
+
formattedUrl = formatDriveShareLink(formattedUrl);
|
|
4468
4530
|
let parsed;
|
|
4469
4531
|
try {
|
|
4470
|
-
parsed = new URL(
|
|
4532
|
+
parsed = new URL(formattedUrl);
|
|
4471
4533
|
} catch {
|
|
4472
4534
|
return rxjs.throwError(error);
|
|
4473
4535
|
}
|
|
4474
|
-
return parsed && !parsed.protocol.match(/http:|https:/) ? rxjs.throwError(error) : rxjs.of(
|
|
4536
|
+
return parsed && !parsed.protocol.match(/http:|https:/) ? rxjs.throwError(error) : rxjs.of(formattedUrl);
|
|
4475
4537
|
}
|
|
4476
4538
|
function optionsFromFile(opts, file) {
|
|
4477
4539
|
if (!(typeof window > "u" || !(file instanceof window.File)))
|
|
@@ -4952,13 +5014,14 @@ function useMediaMetadata(stagedUpload) {
|
|
|
4952
5014
|
setIsLoadingMetadata(!1);
|
|
4953
5015
|
},
|
|
4954
5016
|
() => {
|
|
4955
|
-
const duration = videoElement.duration, width = videoElement.videoWidth, height = videoElement.videoHeight, isAudioOnly = width <= 0 && height <= 0;
|
|
5017
|
+
const duration = videoElement.duration, width = videoElement.videoWidth, height = videoElement.videoHeight, isAudioOnly = width <= 0 && height <= 0, aspectRatio = width / height;
|
|
4956
5018
|
setVideoAssetMetadata((old) => ({
|
|
4957
5019
|
...old,
|
|
4958
5020
|
duration,
|
|
4959
5021
|
width,
|
|
4960
5022
|
height,
|
|
4961
|
-
isAudioOnly
|
|
5023
|
+
isAudioOnly,
|
|
5024
|
+
aspectRatio
|
|
4962
5025
|
}));
|
|
4963
5026
|
}
|
|
4964
5027
|
], cleanupVideo = (videoEl) => {
|
|
@@ -4980,6 +5043,51 @@ function useMediaMetadata(stagedUpload) {
|
|
|
4980
5043
|
isLoadingMetadata
|
|
4981
5044
|
};
|
|
4982
5045
|
}
|
|
5046
|
+
function convertWatermarkToMuxOverlay(watermark, options) {
|
|
5047
|
+
if (!watermark.enabled || !watermark.imageUrl)
|
|
5048
|
+
return null;
|
|
5049
|
+
const size = watermark.size || 20, opacity = watermark.opacity ?? 0.7, toPxString = (valuePercent, axis) => {
|
|
5050
|
+
const videoAspectRatio2 = options?.videoAspectRatio ?? 1.7777777777777777, isVertical = videoAspectRatio2 > 0 && videoAspectRatio2 < 1, base = axis === "x" ? isVertical ? 1080 : 1920 : isVertical ? 1920 : 1080, px = valuePercent / 100 * base;
|
|
5051
|
+
let rounded = Math.round(px);
|
|
5052
|
+
return rounded === 0 && (rounded = px < 0 ? -1 : 1), `${rounded}px`;
|
|
5053
|
+
}, normalizeToPixels = (value, axis) => {
|
|
5054
|
+
if (!value) return value;
|
|
5055
|
+
const trimmed = value.trim();
|
|
5056
|
+
if (trimmed.endsWith("px"))
|
|
5057
|
+
return roundPxString(trimmed);
|
|
5058
|
+
if (trimmed.endsWith("%")) {
|
|
5059
|
+
const n = Number(trimmed.slice(0, -1));
|
|
5060
|
+
return Number.isFinite(n) ? toPxString(n, axis) : value;
|
|
5061
|
+
}
|
|
5062
|
+
return value;
|
|
5063
|
+
};
|
|
5064
|
+
if (watermark.overlay_settings) {
|
|
5065
|
+
const widthValue = watermark.overlay_settings.width, widthNormalized = options?.units === "px" ? normalizeToPixels(widthValue, "x") : widthValue;
|
|
5066
|
+
return {
|
|
5067
|
+
...watermark.overlay_settings,
|
|
5068
|
+
horizontal_margin: options?.units === "px" ? normalizeToPixels(watermark.overlay_settings.horizontal_margin, "x") ?? watermark.overlay_settings.horizontal_margin : watermark.overlay_settings.horizontal_margin,
|
|
5069
|
+
vertical_margin: options?.units === "px" ? normalizeToPixels(watermark.overlay_settings.vertical_margin, "y") ?? watermark.overlay_settings.vertical_margin : watermark.overlay_settings.vertical_margin,
|
|
5070
|
+
width: widthNormalized ?? `${size}%`,
|
|
5071
|
+
opacity: watermark.overlay_settings.opacity ?? `${Math.round(opacity * 100)}%`
|
|
5072
|
+
};
|
|
5073
|
+
}
|
|
5074
|
+
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(
|
|
5075
|
+
0,
|
|
5076
|
+
Math.min(100, size * videoAspectRatio / imageAspectRatio)
|
|
5077
|
+
), halfWidth = watermarkWidthPercentOfVideoWidth / 2, halfHeight = watermarkHeightPercentOfVideoHeight / 2, leftMargin = clampPercent(
|
|
5078
|
+
Math.min(position.x - halfWidth, 100 - watermarkWidthPercentOfVideoWidth)
|
|
5079
|
+
), topMargin = clampPercent(
|
|
5080
|
+
Math.min(position.y - halfHeight, 100 - watermarkHeightPercentOfVideoHeight)
|
|
5081
|
+
), 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}%`;
|
|
5082
|
+
return {
|
|
5083
|
+
vertical_align: "top",
|
|
5084
|
+
vertical_margin: marginY,
|
|
5085
|
+
horizontal_align: "left",
|
|
5086
|
+
horizontal_margin: marginX,
|
|
5087
|
+
width,
|
|
5088
|
+
opacity: `${Math.round(opacity * 100)}%`
|
|
5089
|
+
};
|
|
5090
|
+
}
|
|
4983
5091
|
function formatBytes(bytes, si = !1, dp = 1) {
|
|
4984
5092
|
const thresh = si ? 1e3 : 1024;
|
|
4985
5093
|
if (Math.abs(bytes) < thresh)
|
|
@@ -4992,6 +5100,566 @@ function formatBytes(bytes, si = !1, dp = 1) {
|
|
|
4992
5100
|
while (Math.round(Math.abs(bytes) * r) / r >= thresh && u2 < units.length - 1);
|
|
4993
5101
|
return bytes.toFixed(dp) + " " + units[u2];
|
|
4994
5102
|
}
|
|
5103
|
+
const RangeInput = styledComponents.styled.input`
|
|
5104
|
+
width: 100%;
|
|
5105
|
+
height: 4px;
|
|
5106
|
+
border-radius: 2px;
|
|
5107
|
+
background: var(--card-border-color);
|
|
5108
|
+
outline: none;
|
|
5109
|
+
-webkit-appearance: none;
|
|
5110
|
+
appearance: none;
|
|
5111
|
+
|
|
5112
|
+
&::-webkit-slider-thumb {
|
|
5113
|
+
-webkit-appearance: none;
|
|
5114
|
+
appearance: none;
|
|
5115
|
+
width: 16px;
|
|
5116
|
+
height: 16px;
|
|
5117
|
+
border-radius: 50%;
|
|
5118
|
+
background: var(--card-focus-ring-color, #2276fc);
|
|
5119
|
+
cursor: pointer;
|
|
5120
|
+
border: 2px solid white;
|
|
5121
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5124
|
+
&::-moz-range-thumb {
|
|
5125
|
+
width: 16px;
|
|
5126
|
+
height: 16px;
|
|
5127
|
+
border-radius: 50%;
|
|
5128
|
+
background: var(--card-focus-ring-color, #2276fc);
|
|
5129
|
+
cursor: pointer;
|
|
5130
|
+
border: 2px solid white;
|
|
5131
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
5132
|
+
}
|
|
5133
|
+
|
|
5134
|
+
&:hover::-webkit-slider-thumb {
|
|
5135
|
+
background: var(--card-focus-ring-color, #1a5fc7);
|
|
5136
|
+
}
|
|
5137
|
+
|
|
5138
|
+
&:hover::-moz-range-thumb {
|
|
5139
|
+
background: var(--card-focus-ring-color, #1a5fc7);
|
|
5140
|
+
}
|
|
5141
|
+
`, WatermarkOverlay = styledComponents.styled.div`
|
|
5142
|
+
position: absolute;
|
|
5143
|
+
max-width: 200px;
|
|
5144
|
+
opacity: ${(props) => props.$opacity};
|
|
5145
|
+
cursor: move;
|
|
5146
|
+
user-select: none;
|
|
5147
|
+
z-index: 10;
|
|
5148
|
+
pointer-events: auto;
|
|
5149
|
+
|
|
5150
|
+
img {
|
|
5151
|
+
width: 100%;
|
|
5152
|
+
height: auto;
|
|
5153
|
+
display: block;
|
|
5154
|
+
pointer-events: none;
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5157
|
+
&:hover {
|
|
5158
|
+
outline: 2px dashed rgba(255, 255, 255, 0.8);
|
|
5159
|
+
outline-offset: 4px;
|
|
5160
|
+
}
|
|
5161
|
+
`;
|
|
5162
|
+
function DraggableWatermark({
|
|
5163
|
+
watermark,
|
|
5164
|
+
onChange,
|
|
5165
|
+
containerRef,
|
|
5166
|
+
videoElementRef
|
|
5167
|
+
}) {
|
|
5168
|
+
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) => {
|
|
5169
|
+
if (!value) return null;
|
|
5170
|
+
const trimmed = value.trim();
|
|
5171
|
+
if (!trimmed.endsWith("%")) return null;
|
|
5172
|
+
const num = Number(trimmed.slice(0, -1));
|
|
5173
|
+
return Number.isFinite(num) ? Math.max(0, Math.min(1, num / 100)) : null;
|
|
5174
|
+
}, getVideoContentBox = React.useCallback(() => {
|
|
5175
|
+
const container = containerRef?.current;
|
|
5176
|
+
if (!container) return { x: 0, y: 0, width: 0, height: 0 };
|
|
5177
|
+
const rect = container.getBoundingClientRect(), containerW = rect.width, containerH = rect.height, videoEl = videoElementRef?.current, videoW = videoEl?.videoWidth || 0, videoH = videoEl?.videoHeight || 0;
|
|
5178
|
+
if (!videoW || !videoH || !containerW || !containerH)
|
|
5179
|
+
return { x: 0, y: 0, width: containerW, height: containerH };
|
|
5180
|
+
const scale = Math.min(containerW / videoW, containerH / videoH), contentW = videoW * scale, contentH = videoH * scale, offsetX = (containerW - contentW) / 2, offsetY = (containerH - contentH) / 2;
|
|
5181
|
+
return { x: offsetX, y: offsetY, width: contentW, height: contentH };
|
|
5182
|
+
}, [containerRef, videoElementRef]), parseOverlayValue = (value) => {
|
|
5183
|
+
if (!value) return null;
|
|
5184
|
+
const trimmed = value.trim(), px = trimmed.endsWith("px"), pct = trimmed.endsWith("%"), num = Number(trimmed.replace(/px|%/g, ""));
|
|
5185
|
+
return Number.isFinite(num) ? px ? { n: num, unit: "px" } : pct ? { n: num, unit: "%" } : null : null;
|
|
5186
|
+
}, computeManualStyle = (overlay) => {
|
|
5187
|
+
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) => {
|
|
5188
|
+
if (v)
|
|
5189
|
+
return v.unit === "%" ? `${v.n}%` : axis === "x" ? `${v.n * w / baseW}px` : `${v.n * h / baseH}px`;
|
|
5190
|
+
}, 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();
|
|
5191
|
+
let transform = hStyle.transform;
|
|
5192
|
+
return overlay.vertical_align === "middle" && (transform = overlay.horizontal_align === "center" ? "translate(-50%, -50%)" : "translate(0, -50%)"), {
|
|
5193
|
+
position: "absolute",
|
|
5194
|
+
...hStyle,
|
|
5195
|
+
...vStyle,
|
|
5196
|
+
transform,
|
|
5197
|
+
width: ww ? toCss(ww, "x") : `${size}%`,
|
|
5198
|
+
opacity: manualOpacity ?? opacity,
|
|
5199
|
+
cursor: "default"
|
|
5200
|
+
};
|
|
5201
|
+
}, debouncedOnChange = React.useCallback(
|
|
5202
|
+
(newWatermark) => {
|
|
5203
|
+
debounceTimeoutRef.current && clearTimeout(debounceTimeoutRef.current), debounceTimeoutRef.current = setTimeout(() => {
|
|
5204
|
+
onChange(newWatermark);
|
|
5205
|
+
}, 300);
|
|
5206
|
+
},
|
|
5207
|
+
[onChange]
|
|
5208
|
+
);
|
|
5209
|
+
React.useEffect(() => () => {
|
|
5210
|
+
debounceTimeoutRef.current && clearTimeout(debounceTimeoutRef.current);
|
|
5211
|
+
}, []), React.useEffect(() => {
|
|
5212
|
+
!isDragging && watermark.position && setLocalPosition(watermark.position);
|
|
5213
|
+
}, [watermark.position, isDragging]);
|
|
5214
|
+
const handleMouseDown = React.useCallback(
|
|
5215
|
+
(e) => {
|
|
5216
|
+
e.preventDefault(), setIsDragging(!0), setDragStart({ x: e.clientX, y: e.clientY }), setStartPosition({ x: position.x, y: position.y });
|
|
5217
|
+
},
|
|
5218
|
+
[position]
|
|
5219
|
+
), handleMouseMove = React.useCallback(
|
|
5220
|
+
(e) => {
|
|
5221
|
+
if (!isDragging || !containerRef?.current) return;
|
|
5222
|
+
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;
|
|
5223
|
+
let newX = startPosition.x + deltaXPercent, newY = startPosition.y + deltaYPercent;
|
|
5224
|
+
newX = Math.max(0, Math.min(100, newX)), newY = Math.max(0, Math.min(100, newY)), setLocalPosition({ x: newX, y: newY }), debouncedOnChange({
|
|
5225
|
+
...watermark,
|
|
5226
|
+
position: { x: newX, y: newY }
|
|
5227
|
+
});
|
|
5228
|
+
},
|
|
5229
|
+
[
|
|
5230
|
+
isDragging,
|
|
5231
|
+
dragStart,
|
|
5232
|
+
startPosition,
|
|
5233
|
+
containerRef,
|
|
5234
|
+
watermark,
|
|
5235
|
+
debouncedOnChange,
|
|
5236
|
+
getVideoContentBox
|
|
5237
|
+
]
|
|
5238
|
+
), handleMouseUp = React.useCallback(() => {
|
|
5239
|
+
setIsDragging(!1), debounceTimeoutRef.current && (clearTimeout(debounceTimeoutRef.current), debounceTimeoutRef.current = null), onChange({
|
|
5240
|
+
...watermark,
|
|
5241
|
+
position: localPosition
|
|
5242
|
+
});
|
|
5243
|
+
}, [watermark, localPosition, onChange]);
|
|
5244
|
+
if (React.useEffect(() => {
|
|
5245
|
+
if (isDragging)
|
|
5246
|
+
return document.addEventListener("mousemove", handleMouseMove), document.addEventListener("mouseup", handleMouseUp), () => {
|
|
5247
|
+
document.removeEventListener("mousemove", handleMouseMove), document.removeEventListener("mouseup", handleMouseUp);
|
|
5248
|
+
};
|
|
5249
|
+
}, [isDragging, handleMouseMove, handleMouseUp]), !watermark.imageUrl)
|
|
5250
|
+
return null;
|
|
5251
|
+
const hasManualOverlay = !!watermark.overlay_settings, opacityForRender = hasManualOverlay ? parseOpacityPercent(watermark.overlay_settings?.opacity) ?? opacity : opacity, contentBox = getVideoContentBox(), hasContentBox = contentBox.width > 0 && contentBox.height > 0;
|
|
5252
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5253
|
+
WatermarkOverlay,
|
|
5254
|
+
{
|
|
5255
|
+
ref: watermarkRef,
|
|
5256
|
+
$opacity: opacityForRender,
|
|
5257
|
+
onMouseDown: hasManualOverlay ? void 0 : handleMouseDown,
|
|
5258
|
+
style: hasManualOverlay ? computeManualStyle(watermark.overlay_settings) : hasContentBox ? {
|
|
5259
|
+
left: `${contentBox.x + position.x / 100 * contentBox.width}px`,
|
|
5260
|
+
top: `${contentBox.y + position.y / 100 * contentBox.height}px`,
|
|
5261
|
+
transform: "translate(-50%, -50%)",
|
|
5262
|
+
width: `${Math.max(1, size / 100 * contentBox.width)}px`,
|
|
5263
|
+
cursor: isDragging ? "grabbing" : "grab"
|
|
5264
|
+
} : {
|
|
5265
|
+
left: `${position.x}%`,
|
|
5266
|
+
top: `${position.y}%`,
|
|
5267
|
+
transform: "translate(-50%, -50%)",
|
|
5268
|
+
width: `${size}%`,
|
|
5269
|
+
cursor: isDragging ? "grabbing" : "grab"
|
|
5270
|
+
},
|
|
5271
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: watermark.imageUrl, alt: "Watermark", draggable: !1 })
|
|
5272
|
+
}
|
|
5273
|
+
);
|
|
5274
|
+
}
|
|
5275
|
+
function WatermarkControls({
|
|
5276
|
+
watermark,
|
|
5277
|
+
onChange,
|
|
5278
|
+
onValidationChange,
|
|
5279
|
+
previewContainerRef,
|
|
5280
|
+
previewVideoRef
|
|
5281
|
+
}) {
|
|
5282
|
+
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(
|
|
5283
|
+
watermark.overlay_settings ? "manual" : "canvas"
|
|
5284
|
+
), isUpdatingRef = React.useRef(!1), isValidExtension = (extension) => extension.endsWith(".png") || extension.endsWith(".jpg") || extension.endsWith(".jpeg"), validateUrl = React.useCallback(
|
|
5285
|
+
(url) => {
|
|
5286
|
+
if (validationTimeoutRef.current && clearTimeout(validationTimeoutRef.current), !url) {
|
|
5287
|
+
setUrlError(null), setIsValid(null), setIsValidating(!1), onValidationChange?.(null), isUpdatingRef.current = !0, onChange({
|
|
5288
|
+
...watermark,
|
|
5289
|
+
enabled: !1,
|
|
5290
|
+
imageUrl: void 0,
|
|
5291
|
+
overlay_settings: void 0
|
|
5292
|
+
});
|
|
5293
|
+
return;
|
|
5294
|
+
}
|
|
5295
|
+
setIsValidating(!0), setIsValid(null), setUrlError(null), validationTimeoutRef.current = setTimeout(() => {
|
|
5296
|
+
try {
|
|
5297
|
+
const pathname = new URL(url).pathname.toLowerCase();
|
|
5298
|
+
if (isValidExtension(pathname)) {
|
|
5299
|
+
setIsValid(!0), setUrlError(null), onValidationChange?.(null);
|
|
5300
|
+
const img = new Image();
|
|
5301
|
+
img.onload = () => {
|
|
5302
|
+
const imageAspectRatio = img.naturalWidth && img.naturalHeight ? img.naturalWidth / img.naturalHeight : 1;
|
|
5303
|
+
isUpdatingRef.current = !0, onChange({
|
|
5304
|
+
...watermark,
|
|
5305
|
+
enabled: !0,
|
|
5306
|
+
imageUrl: url,
|
|
5307
|
+
imageAspectRatio
|
|
5308
|
+
});
|
|
5309
|
+
}, img.onerror = () => {
|
|
5310
|
+
isUpdatingRef.current = !0, onChange({
|
|
5311
|
+
...watermark,
|
|
5312
|
+
enabled: !0,
|
|
5313
|
+
imageUrl: url,
|
|
5314
|
+
imageAspectRatio: watermark.imageAspectRatio
|
|
5315
|
+
});
|
|
5316
|
+
}, img.src = url;
|
|
5317
|
+
} else {
|
|
5318
|
+
const errorMsg = "Mux only supports PNG and JPG watermark images. Please use a .png or .jpg file.";
|
|
5319
|
+
setIsValid(!1), setUrlError(errorMsg), onValidationChange?.(errorMsg), isUpdatingRef.current = !0, onChange({
|
|
5320
|
+
...watermark,
|
|
5321
|
+
enabled: !1,
|
|
5322
|
+
imageUrl: void 0,
|
|
5323
|
+
imageAspectRatio: void 0,
|
|
5324
|
+
overlay_settings: void 0
|
|
5325
|
+
});
|
|
5326
|
+
}
|
|
5327
|
+
} catch {
|
|
5328
|
+
setIsValid(!1);
|
|
5329
|
+
const errorMsg = "Please enter a valid URL (e.g., https://example.com/watermark.png)";
|
|
5330
|
+
setUrlError(errorMsg), onValidationChange?.(errorMsg), isUpdatingRef.current = !0, onChange({
|
|
5331
|
+
...watermark,
|
|
5332
|
+
enabled: !1,
|
|
5333
|
+
imageUrl: void 0,
|
|
5334
|
+
imageAspectRatio: void 0,
|
|
5335
|
+
overlay_settings: void 0
|
|
5336
|
+
});
|
|
5337
|
+
} finally {
|
|
5338
|
+
setIsValidating(!1);
|
|
5339
|
+
}
|
|
5340
|
+
}, 500);
|
|
5341
|
+
},
|
|
5342
|
+
[watermark, onChange, onValidationChange]
|
|
5343
|
+
);
|
|
5344
|
+
React.useEffect(() => () => {
|
|
5345
|
+
validationTimeoutRef.current && clearTimeout(validationTimeoutRef.current);
|
|
5346
|
+
}, []), React.useEffect(() => {
|
|
5347
|
+
setMode(watermark.overlay_settings ? "manual" : "canvas");
|
|
5348
|
+
}, [watermark.overlay_settings]);
|
|
5349
|
+
const handleUrlChange = (e) => {
|
|
5350
|
+
const url = e.target.value;
|
|
5351
|
+
setUrlInput(url), watermark.imageUrl && url !== watermark.imageUrl && (isUpdatingRef.current = !0, onChange({
|
|
5352
|
+
...watermark,
|
|
5353
|
+
enabled: !1,
|
|
5354
|
+
imageUrl: void 0,
|
|
5355
|
+
imageAspectRatio: void 0,
|
|
5356
|
+
overlay_settings: void 0
|
|
5357
|
+
})), validateUrl(url);
|
|
5358
|
+
}, normalizeZeroPercent = (value) => {
|
|
5359
|
+
if (!value) return value;
|
|
5360
|
+
const trimmed = value.trim();
|
|
5361
|
+
if (!trimmed.endsWith("%")) return value;
|
|
5362
|
+
const n = Number(trimmed.slice(0, -1));
|
|
5363
|
+
return Number.isFinite(n) ? n === 0 || Object.is(n, -0) || Math.abs(n) < 1e-9 ? "0.01%" : `${n}%` : value;
|
|
5364
|
+
}, updateOverlaySettings = (next) => {
|
|
5365
|
+
const merged = {
|
|
5366
|
+
...watermark.overlay_settings ?? {
|
|
5367
|
+
vertical_align: "bottom",
|
|
5368
|
+
vertical_margin: "2%",
|
|
5369
|
+
horizontal_align: "right",
|
|
5370
|
+
horizontal_margin: "2%",
|
|
5371
|
+
width: `${watermark.size ?? 20}%`,
|
|
5372
|
+
opacity: `${Math.round((watermark.opacity ?? 0.7) * 100)}%`
|
|
5373
|
+
},
|
|
5374
|
+
...next
|
|
5375
|
+
};
|
|
5376
|
+
onChange({
|
|
5377
|
+
...watermark,
|
|
5378
|
+
enabled: !0,
|
|
5379
|
+
overlay_settings: {
|
|
5380
|
+
...merged,
|
|
5381
|
+
horizontal_margin: normalizeZeroPercent(merged.horizontal_margin) || merged.horizontal_margin,
|
|
5382
|
+
vertical_margin: normalizeZeroPercent(merged.vertical_margin) || merged.vertical_margin
|
|
5383
|
+
}
|
|
5384
|
+
});
|
|
5385
|
+
}, getVideoContentBox = () => {
|
|
5386
|
+
const container = previewContainerRef?.current;
|
|
5387
|
+
if (!container) return { x: 0, y: 0, width: 0, height: 0 };
|
|
5388
|
+
const rect = container.getBoundingClientRect(), containerW = rect.width, containerH = rect.height, videoEl = previewVideoRef?.current, videoW = videoEl?.videoWidth || 0, videoH = videoEl?.videoHeight || 0;
|
|
5389
|
+
if (!videoW || !videoH || !containerW || !containerH)
|
|
5390
|
+
return { x: 0, y: 0, width: containerW, height: containerH };
|
|
5391
|
+
const scale = Math.min(containerW / videoW, containerH / videoH), contentW = videoW * scale, contentH = videoH * scale, offsetX = (containerW - contentW) / 2, offsetY = (containerH - contentH) / 2;
|
|
5392
|
+
return { x: offsetX, y: offsetY, width: contentW, height: contentH };
|
|
5393
|
+
};
|
|
5394
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
5395
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
5396
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "medium", children: "Watermark Image URL" }),
|
|
5397
|
+
/* @__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." }),
|
|
5398
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Box, { style: { position: "relative", width: "100%" }, children: [
|
|
5399
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5400
|
+
"input",
|
|
5401
|
+
{
|
|
5402
|
+
type: "url",
|
|
5403
|
+
value: urlInput,
|
|
5404
|
+
onChange: handleUrlChange,
|
|
5405
|
+
placeholder: "https://example.com/watermark.png",
|
|
5406
|
+
style: {
|
|
5407
|
+
padding: "8px 12px",
|
|
5408
|
+
paddingRight: urlInput ? "96px" : isValid !== null ? "36px" : "12px",
|
|
5409
|
+
border: urlError || isValid === !1 ? "1px solid #e74c3c" : isValid === !0 ? "1px solid #4caf50" : "1px solid #ccc",
|
|
5410
|
+
borderRadius: "4px",
|
|
5411
|
+
width: "100%",
|
|
5412
|
+
maxWidth: "100%",
|
|
5413
|
+
boxSizing: "border-box",
|
|
5414
|
+
fontSize: "14px"
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
),
|
|
5418
|
+
(urlInput || isValidating || isValid !== null) && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5419
|
+
ui.Box,
|
|
5420
|
+
{
|
|
5421
|
+
style: {
|
|
5422
|
+
position: "absolute",
|
|
5423
|
+
right: "8px",
|
|
5424
|
+
top: "50%",
|
|
5425
|
+
transform: "translateY(-50%)",
|
|
5426
|
+
display: "flex",
|
|
5427
|
+
alignItems: "center",
|
|
5428
|
+
gap: "4px"
|
|
5429
|
+
},
|
|
5430
|
+
children: [
|
|
5431
|
+
urlInput && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5432
|
+
ui.Button,
|
|
5433
|
+
{
|
|
5434
|
+
text: "Clear",
|
|
5435
|
+
mode: "bleed",
|
|
5436
|
+
tone: "critical",
|
|
5437
|
+
onClick: () => {
|
|
5438
|
+
setUrlInput(""), validateUrl("");
|
|
5439
|
+
},
|
|
5440
|
+
disabled: isValidating,
|
|
5441
|
+
style: { fontSize: "11px", height: "24px" }
|
|
5442
|
+
}
|
|
5443
|
+
),
|
|
5444
|
+
isValidating && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, muted: !0, children: "Validating..." }),
|
|
5445
|
+
isValid === !0 && !isValidating && /* @__PURE__ */ jsxRuntime.jsx(icons.CheckmarkCircleIcon, { style: { color: "#4caf50", fontSize: "18px" } }),
|
|
5446
|
+
isValid === !1 && !isValidating && /* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, { style: { color: "#e74c3c", fontSize: "18px" } })
|
|
5447
|
+
]
|
|
5448
|
+
}
|
|
5449
|
+
)
|
|
5450
|
+
] }),
|
|
5451
|
+
urlError && /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 2, tone: "critical", radius: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
|
|
5452
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, { style: { color: "#e74c3c", flexShrink: 0 } }),
|
|
5453
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, style: { color: "#e74c3c" }, children: urlError })
|
|
5454
|
+
] }) })
|
|
5455
|
+
] }),
|
|
5456
|
+
watermark.imageUrl && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
5457
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 3, tone: "transparent", border: !0, radius: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5458
|
+
ui.Flex,
|
|
5459
|
+
{
|
|
5460
|
+
align: "center",
|
|
5461
|
+
justify: "space-between",
|
|
5462
|
+
gap: 3,
|
|
5463
|
+
style: { flexWrap: "wrap", alignItems: "flex-start" },
|
|
5464
|
+
children: [
|
|
5465
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, style: { minWidth: 240, flex: 1 }, children: [
|
|
5466
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "medium", children: "Positioning mode" }),
|
|
5467
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 0, muted: !0, children: [
|
|
5468
|
+
"Choose between dragging on the canvas or manually editing the Mux",
|
|
5469
|
+
" ",
|
|
5470
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "overlay_settings" }),
|
|
5471
|
+
" fields (as in",
|
|
5472
|
+
" ",
|
|
5473
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5474
|
+
"a",
|
|
5475
|
+
{
|
|
5476
|
+
href: "https://www.mux.com/docs/guides/add-watermarks-to-your-videos",
|
|
5477
|
+
target: "_blank",
|
|
5478
|
+
rel: "noopener noreferrer",
|
|
5479
|
+
children: "the docs"
|
|
5480
|
+
}
|
|
5481
|
+
),
|
|
5482
|
+
")."
|
|
5483
|
+
] })
|
|
5484
|
+
] }),
|
|
5485
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 2, style: { flexWrap: "wrap" }, children: [
|
|
5486
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5487
|
+
ui.Button,
|
|
5488
|
+
{
|
|
5489
|
+
text: "Canvas",
|
|
5490
|
+
mode: mode === "canvas" ? "default" : "ghost",
|
|
5491
|
+
onClick: () => {
|
|
5492
|
+
setMode("canvas"), onChange({ ...watermark, enabled: !0, overlay_settings: void 0 });
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5495
|
+
),
|
|
5496
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5497
|
+
ui.Button,
|
|
5498
|
+
{
|
|
5499
|
+
text: "Manual",
|
|
5500
|
+
mode: mode === "manual" ? "default" : "ghost",
|
|
5501
|
+
onClick: () => {
|
|
5502
|
+
setMode("manual");
|
|
5503
|
+
const overlay = convertWatermarkToMuxOverlay({ ...watermark, enabled: !0 });
|
|
5504
|
+
updateOverlaySettings(overlay ?? {});
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
)
|
|
5508
|
+
] })
|
|
5509
|
+
]
|
|
5510
|
+
}
|
|
5511
|
+
) }),
|
|
5512
|
+
mode === "manual" && /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 3, tone: "transparent", border: !0, radius: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
5513
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "medium", children: "Mux overlay_settings" }),
|
|
5514
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Grid, { columns: [1, 2], gap: 3, style: { width: "100%" }, children: [
|
|
5515
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5516
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, muted: !0, children: "horizontal_align" }),
|
|
5517
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5518
|
+
"select",
|
|
5519
|
+
{
|
|
5520
|
+
value: watermark.overlay_settings?.horizontal_align || "right",
|
|
5521
|
+
onChange: (e) => updateOverlaySettings({
|
|
5522
|
+
horizontal_align: e.target.value || "right"
|
|
5523
|
+
}),
|
|
5524
|
+
style: {
|
|
5525
|
+
width: "100%",
|
|
5526
|
+
padding: "8px 10px",
|
|
5527
|
+
border: "1px solid #ccc",
|
|
5528
|
+
borderRadius: 4
|
|
5529
|
+
},
|
|
5530
|
+
children: [
|
|
5531
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "left", children: "left" }),
|
|
5532
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "center", children: "center" }),
|
|
5533
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "right", children: "right" })
|
|
5534
|
+
]
|
|
5535
|
+
}
|
|
5536
|
+
)
|
|
5537
|
+
] }),
|
|
5538
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5539
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, muted: !0, children: "horizontal_margin (e.g. 2% or 40px)" }),
|
|
5540
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5541
|
+
ui.TextInput,
|
|
5542
|
+
{
|
|
5543
|
+
value: watermark.overlay_settings?.horizontal_margin || "2%",
|
|
5544
|
+
onChange: (e) => updateOverlaySettings({ horizontal_margin: e.currentTarget.value })
|
|
5545
|
+
}
|
|
5546
|
+
)
|
|
5547
|
+
] }),
|
|
5548
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5549
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, muted: !0, children: "vertical_align" }),
|
|
5550
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5551
|
+
"select",
|
|
5552
|
+
{
|
|
5553
|
+
value: watermark.overlay_settings?.vertical_align || "bottom",
|
|
5554
|
+
onChange: (e) => updateOverlaySettings({
|
|
5555
|
+
vertical_align: e.target.value || "bottom"
|
|
5556
|
+
}),
|
|
5557
|
+
style: {
|
|
5558
|
+
width: "100%",
|
|
5559
|
+
padding: "8px 10px",
|
|
5560
|
+
border: "1px solid #ccc",
|
|
5561
|
+
borderRadius: 4
|
|
5562
|
+
},
|
|
5563
|
+
children: [
|
|
5564
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "top", children: "top" }),
|
|
5565
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "middle", children: "middle" }),
|
|
5566
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "bottom", children: "bottom" })
|
|
5567
|
+
]
|
|
5568
|
+
}
|
|
5569
|
+
)
|
|
5570
|
+
] }),
|
|
5571
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5572
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, muted: !0, children: "vertical_margin (e.g. 2% or 40px)" }),
|
|
5573
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5574
|
+
ui.TextInput,
|
|
5575
|
+
{
|
|
5576
|
+
value: watermark.overlay_settings?.vertical_margin || "2%",
|
|
5577
|
+
onChange: (e) => updateOverlaySettings({ vertical_margin: e.currentTarget.value })
|
|
5578
|
+
}
|
|
5579
|
+
)
|
|
5580
|
+
] }),
|
|
5581
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5582
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, muted: !0, children: "width (e.g. 25% or 80px)" }),
|
|
5583
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5584
|
+
ui.TextInput,
|
|
5585
|
+
{
|
|
5586
|
+
value: watermark.overlay_settings?.width || `${watermark.size ?? 20}%`,
|
|
5587
|
+
onChange: (e) => updateOverlaySettings({ width: e.currentTarget.value })
|
|
5588
|
+
}
|
|
5589
|
+
)
|
|
5590
|
+
] }),
|
|
5591
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, style: { minWidth: 0 }, children: [
|
|
5592
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, muted: !0, children: "opacity (e.g. 90%)" }),
|
|
5593
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5594
|
+
ui.TextInput,
|
|
5595
|
+
{
|
|
5596
|
+
value: watermark.overlay_settings?.opacity || `${Math.round((watermark.opacity ?? 0.7) * 100)}%`,
|
|
5597
|
+
onChange: (e) => updateOverlaySettings({ opacity: e.currentTarget.value })
|
|
5598
|
+
}
|
|
5599
|
+
)
|
|
5600
|
+
] })
|
|
5601
|
+
] }),
|
|
5602
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, muted: !0, children: "Margins and width accept either percentages or pixels, per the Mux guide." })
|
|
5603
|
+
] }) }),
|
|
5604
|
+
mode === "canvas" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5605
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Box, { children: [
|
|
5606
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "medium", children: (() => {
|
|
5607
|
+
const sizePct = watermark.size || 20, contentW = getVideoContentBox().width;
|
|
5608
|
+
return contentW ? `Size: ${Math.max(1, Math.round(sizePct / 100 * contentW))}px` : `Size: ${sizePct}%`;
|
|
5609
|
+
})() }),
|
|
5610
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5611
|
+
RangeInput,
|
|
5612
|
+
{
|
|
5613
|
+
type: "range",
|
|
5614
|
+
value: (() => {
|
|
5615
|
+
const sizePct = watermark.size || 20, contentW = getVideoContentBox().width;
|
|
5616
|
+
return contentW ? Math.max(1, Math.round(sizePct / 100 * contentW)) : sizePct;
|
|
5617
|
+
})(),
|
|
5618
|
+
min: (() => {
|
|
5619
|
+
const contentW = getVideoContentBox().width;
|
|
5620
|
+
return contentW ? Math.max(1, Math.round(contentW * 0.05)) : 5;
|
|
5621
|
+
})(),
|
|
5622
|
+
max: (() => {
|
|
5623
|
+
const contentW = getVideoContentBox().width;
|
|
5624
|
+
return contentW ? Math.max(1, Math.round(contentW * 0.5)) : 50;
|
|
5625
|
+
})(),
|
|
5626
|
+
step: 1,
|
|
5627
|
+
onChange: (e) => {
|
|
5628
|
+
const raw = Number(e.target.value), contentW = getVideoContentBox().width, nextPct = contentW ? raw / contentW * 100 : raw, clampedPct = Math.max(5, Math.min(50, nextPct));
|
|
5629
|
+
onChange({
|
|
5630
|
+
...watermark,
|
|
5631
|
+
size: clampedPct
|
|
5632
|
+
});
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
)
|
|
5636
|
+
] }),
|
|
5637
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Box, { children: [
|
|
5638
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 1, weight: "medium", children: [
|
|
5639
|
+
"Opacity: ",
|
|
5640
|
+
Math.round((watermark.opacity ?? 0.7) * 100),
|
|
5641
|
+
"%"
|
|
5642
|
+
] }),
|
|
5643
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5644
|
+
RangeInput,
|
|
5645
|
+
{
|
|
5646
|
+
type: "range",
|
|
5647
|
+
value: watermark.opacity ?? 0.7,
|
|
5648
|
+
min: 0,
|
|
5649
|
+
max: 1,
|
|
5650
|
+
step: 0.05,
|
|
5651
|
+
onChange: (e) => onChange({
|
|
5652
|
+
...watermark,
|
|
5653
|
+
opacity: Number(e.target.value)
|
|
5654
|
+
})
|
|
5655
|
+
}
|
|
5656
|
+
)
|
|
5657
|
+
] })
|
|
5658
|
+
] }),
|
|
5659
|
+
/* @__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" }) })
|
|
5660
|
+
] })
|
|
5661
|
+
] });
|
|
5662
|
+
}
|
|
4995
5663
|
const ALL_LANGUAGE_CODES = LanguagesList__default.default.getAllCodes().map((code) => ({
|
|
4996
5664
|
value: code,
|
|
4997
5665
|
label: LanguagesList__default.default.getNativeName(code)
|
|
@@ -5433,7 +6101,7 @@ function UploadConfiguration({
|
|
|
5433
6101
|
startUpload,
|
|
5434
6102
|
onClose
|
|
5435
6103
|
}) {
|
|
5436
|
-
const id = React.useId(), autoTextTracks = React.useRef(
|
|
6104
|
+
const id = React.useId(), [watermarkValidationError, setWatermarkValidationError] = React.useState(null), watermarkPreviewContainerRef = React.useRef(null), watermarkPreviewVideoRef = React.useRef(null), autoTextTracks = React.useRef(
|
|
5437
6105
|
pluginConfig.video_quality === "plus" && pluginConfig.defaultAutogeneratedSubtitleLang ? [
|
|
5438
6106
|
{
|
|
5439
6107
|
_id: uuid.uuid(),
|
|
@@ -5469,6 +6137,8 @@ function UploadConfiguration({
|
|
|
5469
6137
|
return Object.assign({}, prev, { [action.action]: action.value });
|
|
5470
6138
|
case "drm_policy":
|
|
5471
6139
|
return Object.assign({}, prev, { [action.action]: action.value });
|
|
6140
|
+
case "watermark":
|
|
6141
|
+
return Object.assign({}, prev, { watermark: action.value });
|
|
5472
6142
|
// Updating individual tracks
|
|
5473
6143
|
case "track": {
|
|
5474
6144
|
const text_tracks = [...prev.text_tracks], target_track_i = text_tracks.findIndex(({ _id: _id2 }) => _id2 === action.id);
|
|
@@ -5536,7 +6206,12 @@ function UploadConfiguration({
|
|
|
5536
6206
|
]);
|
|
5537
6207
|
const { disableTextTrackConfig, disableUploadConfig } = pluginConfig, skipConfig = disableTextTrackConfig && disableUploadConfig;
|
|
5538
6208
|
if (React.useEffect(() => {
|
|
5539
|
-
|
|
6209
|
+
if (skipConfig) {
|
|
6210
|
+
const { settings, watermark } = formatUploadConfig(config, secrets, {
|
|
6211
|
+
videoAspectRatio: videoAssetMetadata?.aspectRatio
|
|
6212
|
+
});
|
|
6213
|
+
startUpload(settings, watermark);
|
|
6214
|
+
}
|
|
5540
6215
|
}, []), skipConfig) return null;
|
|
5541
6216
|
const basicConfig = config.video_quality !== "plus" && config.video_quality !== "premium", playbackPolicySelected = config.public_policy || config.signed_policy || config.drm_policy, maxSupportedResolution = RESOLUTION_TIERS.findIndex(
|
|
5542
6217
|
(rt) => rt.value === pluginConfig.max_resolution_tier
|
|
@@ -5552,11 +6227,11 @@ function UploadConfiguration({
|
|
|
5552
6227
|
header: "Configure Mux Upload",
|
|
5553
6228
|
onClose,
|
|
5554
6229
|
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { padding: 4, space: 2, children: [
|
|
5555
|
-
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: [
|
|
6230
|
+
(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: [
|
|
5556
6231
|
/* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, { width: 20, height: 20 }),
|
|
5557
6232
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
5558
6233
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "semibold", children: "Validation Error" }),
|
|
5559
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, children: validationError })
|
|
6234
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, children: validationError || watermarkValidationError })
|
|
5560
6235
|
] })
|
|
5561
6236
|
] }) }),
|
|
5562
6237
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: 3, children: "FILE TO UPLOAD" }),
|
|
@@ -5623,27 +6298,41 @@ function UploadConfiguration({
|
|
|
5623
6298
|
}) })
|
|
5624
6299
|
}
|
|
5625
6300
|
),
|
|
5626
|
-
!basicConfig && /* @__PURE__ */ jsxRuntime.
|
|
5627
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5628
|
-
|
|
5629
|
-
|
|
6301
|
+
!basicConfig && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6302
|
+
/* @__PURE__ */ jsxRuntime.jsx(sanity.FormField, { title: "Additional Configuration", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
6303
|
+
/* @__PURE__ */ jsxRuntime.jsx(PlaybackPolicy, { id, config, secrets, dispatch }),
|
|
6304
|
+
maxSupportedResolution > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6305
|
+
ResolutionTierSelector,
|
|
6306
|
+
{
|
|
6307
|
+
id,
|
|
6308
|
+
config,
|
|
6309
|
+
dispatch,
|
|
6310
|
+
maxSupportedResolution
|
|
6311
|
+
}
|
|
6312
|
+
),
|
|
6313
|
+
/* @__PURE__ */ jsxRuntime.jsx(StaticRenditionSelector, { id, config, dispatch }),
|
|
6314
|
+
!disableTextTrackConfig && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6315
|
+
TextTracksEditor,
|
|
6316
|
+
{
|
|
6317
|
+
tracks: config.text_tracks,
|
|
6318
|
+
dispatch,
|
|
6319
|
+
defaultLang: pluginConfig.defaultAutogeneratedSubtitleLang
|
|
6320
|
+
}
|
|
6321
|
+
)
|
|
6322
|
+
] }) }),
|
|
6323
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6324
|
+
WatermarkSection,
|
|
5630
6325
|
{
|
|
5631
|
-
id,
|
|
5632
6326
|
config,
|
|
5633
6327
|
dispatch,
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
TextTracksEditor,
|
|
5640
|
-
{
|
|
5641
|
-
tracks: config.text_tracks,
|
|
5642
|
-
dispatch,
|
|
5643
|
-
defaultLang: pluginConfig.defaultAutogeneratedSubtitleLang
|
|
6328
|
+
stagedUpload,
|
|
6329
|
+
videoAssetMetadata,
|
|
6330
|
+
watermarkPreviewContainerRef,
|
|
6331
|
+
watermarkPreviewVideoRef,
|
|
6332
|
+
onValidationChange: setWatermarkValidationError
|
|
5644
6333
|
}
|
|
5645
6334
|
)
|
|
5646
|
-
] })
|
|
6335
|
+
] })
|
|
5647
6336
|
] }),
|
|
5648
6337
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5649
6338
|
ui.Button,
|
|
@@ -5653,7 +6342,12 @@ function UploadConfiguration({
|
|
|
5653
6342
|
text: "Upload",
|
|
5654
6343
|
tone: "positive",
|
|
5655
6344
|
onClick: () => {
|
|
5656
|
-
|
|
6345
|
+
if (!validationError) {
|
|
6346
|
+
const { settings, watermark } = formatUploadConfig(config, secrets, {
|
|
6347
|
+
videoAspectRatio: videoAssetMetadata?.aspectRatio
|
|
6348
|
+
});
|
|
6349
|
+
startUpload(settings, watermark);
|
|
6350
|
+
}
|
|
5657
6351
|
}
|
|
5658
6352
|
}
|
|
5659
6353
|
) })
|
|
@@ -5668,36 +6362,178 @@ function setAdvancedPlaybackPolicy(config, secrets) {
|
|
|
5668
6362
|
drm_configuration_id: secrets.drmConfigId ?? void 0
|
|
5669
6363
|
}) : console.error("Selected DRM Policy but missing DRM Configuration Id")), advanced_playback_policies;
|
|
5670
6364
|
}
|
|
5671
|
-
function formatUploadConfig(config, secrets) {
|
|
6365
|
+
function formatUploadConfig(config, secrets, options) {
|
|
5672
6366
|
const generated_subtitles = config.text_tracks.filter(isAutogeneratedTrack).map((track) => ({
|
|
5673
6367
|
name: track.name,
|
|
5674
6368
|
language_code: track.language_code
|
|
5675
|
-
}))
|
|
6369
|
+
})), inputs = [
|
|
6370
|
+
{
|
|
6371
|
+
type: "video",
|
|
6372
|
+
generated_subtitles: generated_subtitles.length > 0 ? generated_subtitles : void 0
|
|
6373
|
+
},
|
|
6374
|
+
...config.text_tracks.filter(isCustomTextTrack).reduce(
|
|
6375
|
+
(acc, track) => (track.language_code && track.file && track.name && acc.push({
|
|
6376
|
+
url: track.file.contents,
|
|
6377
|
+
type: "text",
|
|
6378
|
+
text_type: track.type === "subtitles" ? "subtitles" : void 0,
|
|
6379
|
+
language_code: track.language_code,
|
|
6380
|
+
name: track.name,
|
|
6381
|
+
closed_captions: track.type === "captions"
|
|
6382
|
+
}), acc),
|
|
6383
|
+
[]
|
|
6384
|
+
)
|
|
6385
|
+
];
|
|
6386
|
+
if (config.watermark?.imageUrl) {
|
|
6387
|
+
const watermarkForMux = { ...config.watermark, enabled: !0 }, overlaySettings = convertWatermarkToMuxOverlay(watermarkForMux, {
|
|
6388
|
+
videoAspectRatio: options?.videoAspectRatio ?? void 0,
|
|
6389
|
+
units: "px"
|
|
6390
|
+
});
|
|
6391
|
+
overlaySettings && inputs.push({
|
|
6392
|
+
url: config.watermark.imageUrl,
|
|
6393
|
+
overlay_settings: overlaySettings
|
|
6394
|
+
});
|
|
6395
|
+
}
|
|
5676
6396
|
return {
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
text_type: track.type === "subtitles" ? "subtitles" : void 0,
|
|
5687
|
-
language_code: track.language_code,
|
|
5688
|
-
name: track.name,
|
|
5689
|
-
closed_captions: track.type === "captions"
|
|
5690
|
-
}), acc),
|
|
5691
|
-
[]
|
|
5692
|
-
)
|
|
5693
|
-
],
|
|
5694
|
-
static_renditions: config.static_renditions.length > 0 ? config.static_renditions.map((resolution) => ({ resolution })) : void 0,
|
|
5695
|
-
advanced_playback_policies: setAdvancedPlaybackPolicy(config, secrets),
|
|
5696
|
-
max_resolution_tier: config.max_resolution_tier,
|
|
5697
|
-
video_quality: config.video_quality,
|
|
5698
|
-
normalize_audio: config.normalize_audio
|
|
6397
|
+
settings: {
|
|
6398
|
+
input: inputs,
|
|
6399
|
+
static_renditions: config.static_renditions.length > 0 ? config.static_renditions.map((resolution) => ({ resolution })) : void 0,
|
|
6400
|
+
advanced_playback_policies: setAdvancedPlaybackPolicy(config, secrets),
|
|
6401
|
+
max_resolution_tier: config.max_resolution_tier,
|
|
6402
|
+
video_quality: config.video_quality,
|
|
6403
|
+
normalize_audio: config.normalize_audio
|
|
6404
|
+
},
|
|
6405
|
+
watermark: config.watermark?.imageUrl ? { ...config.watermark, enabled: !0 } : void 0
|
|
5699
6406
|
};
|
|
5700
6407
|
}
|
|
6408
|
+
function WatermarkSection({
|
|
6409
|
+
config,
|
|
6410
|
+
dispatch,
|
|
6411
|
+
stagedUpload,
|
|
6412
|
+
videoAssetMetadata,
|
|
6413
|
+
watermarkPreviewContainerRef,
|
|
6414
|
+
watermarkPreviewVideoRef,
|
|
6415
|
+
onValidationChange
|
|
6416
|
+
}) {
|
|
6417
|
+
return videoAssetMetadata?.isAudioOnly !== !1 ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
6418
|
+
sanity.FormField,
|
|
6419
|
+
{
|
|
6420
|
+
title: "Watermark",
|
|
6421
|
+
description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6422
|
+
"Add a watermark overlay to your video using Mux's native watermark support.",
|
|
6423
|
+
" ",
|
|
6424
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6425
|
+
"a",
|
|
6426
|
+
{
|
|
6427
|
+
href: "https://www.mux.com/docs/guides/add-watermarks-to-your-videos",
|
|
6428
|
+
target: "_blank",
|
|
6429
|
+
rel: "noopener noreferrer",
|
|
6430
|
+
children: "Learn more about Mux watermarks."
|
|
6431
|
+
}
|
|
6432
|
+
)
|
|
6433
|
+
] }),
|
|
6434
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
6435
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6436
|
+
WatermarkControls,
|
|
6437
|
+
{
|
|
6438
|
+
watermark: config.watermark || { enabled: !1 },
|
|
6439
|
+
onChange: (watermark) => {
|
|
6440
|
+
dispatch({ action: "watermark", value: watermark });
|
|
6441
|
+
},
|
|
6442
|
+
onValidationChange,
|
|
6443
|
+
previewContainerRef: watermarkPreviewContainerRef,
|
|
6444
|
+
previewVideoRef: watermarkPreviewVideoRef
|
|
6445
|
+
}
|
|
6446
|
+
),
|
|
6447
|
+
config.watermark?.imageUrl && stagedUpload.type === "file" && // Canvas preview is only shown in "Canvas" mode (no explicit overlay_settings)
|
|
6448
|
+
!config.watermark.overlay_settings && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6449
|
+
WatermarkPreview,
|
|
6450
|
+
{
|
|
6451
|
+
stagedUpload,
|
|
6452
|
+
watermark: config.watermark,
|
|
6453
|
+
videoAspectRatio: videoAssetMetadata.aspectRatio,
|
|
6454
|
+
onWatermarkChange: (watermark) => {
|
|
6455
|
+
dispatch({ action: "watermark", value: watermark });
|
|
6456
|
+
},
|
|
6457
|
+
previewContainerRef: watermarkPreviewContainerRef,
|
|
6458
|
+
videoRef: watermarkPreviewVideoRef
|
|
6459
|
+
}
|
|
6460
|
+
)
|
|
6461
|
+
] })
|
|
6462
|
+
}
|
|
6463
|
+
);
|
|
6464
|
+
}
|
|
6465
|
+
const WatermarkPreview = React.memo(function({
|
|
6466
|
+
stagedUpload,
|
|
6467
|
+
watermark,
|
|
6468
|
+
onWatermarkChange,
|
|
6469
|
+
videoAspectRatio,
|
|
6470
|
+
previewContainerRef,
|
|
6471
|
+
videoRef
|
|
6472
|
+
}) {
|
|
6473
|
+
React.useEffect(() => {
|
|
6474
|
+
if (videoRef.current && stagedUpload.type === "file") {
|
|
6475
|
+
const file = stagedUpload.files[0], url = URL.createObjectURL(file);
|
|
6476
|
+
return videoRef.current.src = url, () => {
|
|
6477
|
+
URL.revokeObjectURL(url);
|
|
6478
|
+
};
|
|
6479
|
+
}
|
|
6480
|
+
}, [stagedUpload, videoRef]);
|
|
6481
|
+
const isVertical = videoAspectRatio != null && videoAspectRatio < 1;
|
|
6482
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
6483
|
+
ui.Card,
|
|
6484
|
+
{
|
|
6485
|
+
tone: "transparent",
|
|
6486
|
+
border: !0,
|
|
6487
|
+
style: {
|
|
6488
|
+
overflow: "hidden",
|
|
6489
|
+
// For vertical videos, center the preview and limit its width
|
|
6490
|
+
display: "flex",
|
|
6491
|
+
justifyContent: "center"
|
|
6492
|
+
},
|
|
6493
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6494
|
+
"div",
|
|
6495
|
+
{
|
|
6496
|
+
ref: previewContainerRef,
|
|
6497
|
+
style: {
|
|
6498
|
+
position: "relative",
|
|
6499
|
+
// For vertical videos: limit width so the preview doesn't get too tall
|
|
6500
|
+
// For horizontal videos: use full width
|
|
6501
|
+
width: isVertical ? "auto" : "100%",
|
|
6502
|
+
aspectRatio: videoAspectRatio ? String(videoAspectRatio) : "16/9",
|
|
6503
|
+
...isVertical ? { height: "400px", maxHeight: "50vh" } : { minHeight: "200px" },
|
|
6504
|
+
overflow: "hidden"
|
|
6505
|
+
},
|
|
6506
|
+
children: [
|
|
6507
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6508
|
+
"video",
|
|
6509
|
+
{
|
|
6510
|
+
ref: videoRef,
|
|
6511
|
+
style: {
|
|
6512
|
+
position: "absolute",
|
|
6513
|
+
top: 0,
|
|
6514
|
+
left: 0,
|
|
6515
|
+
width: "100%",
|
|
6516
|
+
height: "100%",
|
|
6517
|
+
objectFit: "fill",
|
|
6518
|
+
display: "block"
|
|
6519
|
+
}
|
|
6520
|
+
}
|
|
6521
|
+
),
|
|
6522
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6523
|
+
DraggableWatermark,
|
|
6524
|
+
{
|
|
6525
|
+
watermark,
|
|
6526
|
+
onChange: onWatermarkChange,
|
|
6527
|
+
containerRef: previewContainerRef,
|
|
6528
|
+
videoElementRef: videoRef
|
|
6529
|
+
}
|
|
6530
|
+
)
|
|
6531
|
+
]
|
|
6532
|
+
}
|
|
6533
|
+
)
|
|
6534
|
+
}
|
|
6535
|
+
);
|
|
6536
|
+
});
|
|
5701
6537
|
function withFocusRing(component) {
|
|
5702
6538
|
return styledComponents.styled(component)((props) => {
|
|
5703
6539
|
const border = {
|
|
@@ -5936,7 +6772,7 @@ function Uploader(props) {
|
|
|
5936
6772
|
window.removeEventListener("beforeunload", handleBeforeUnload), window.removeEventListener("pagehide", handleBeforeUnload), cleanup();
|
|
5937
6773
|
};
|
|
5938
6774
|
}, [props.client, props.asset?._id]);
|
|
5939
|
-
const startUpload = (settings) => {
|
|
6775
|
+
const startUpload = (settings, watermark) => {
|
|
5940
6776
|
const { stagedUpload } = state;
|
|
5941
6777
|
if (!stagedUpload || uploadRef.current) return;
|
|
5942
6778
|
dispatch({ action: "commitUpload" });
|
|
@@ -5946,14 +6782,16 @@ function Uploader(props) {
|
|
|
5946
6782
|
uploadObservable = uploadUrl({
|
|
5947
6783
|
client: props.client,
|
|
5948
6784
|
url: stagedUpload.url,
|
|
5949
|
-
settings
|
|
6785
|
+
settings,
|
|
6786
|
+
watermark
|
|
5950
6787
|
});
|
|
5951
6788
|
break;
|
|
5952
6789
|
case "file":
|
|
5953
6790
|
uploadObservable = uploadFile({
|
|
5954
6791
|
client: props.client,
|
|
5955
6792
|
file: stagedUpload.files[0],
|
|
5956
|
-
settings
|
|
6793
|
+
settings,
|
|
6794
|
+
watermark
|
|
5957
6795
|
}).pipe(
|
|
5958
6796
|
operators.takeUntil(
|
|
5959
6797
|
cancelUploadButton.observable.pipe(
|