sanity-plugin-mux-input 2.13.0 → 2.14.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.d.mts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +1393 -98
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1394 -99
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/actions/assets.ts +75 -0
- package/src/components/AddCaptionDialog.tsx +421 -0
- package/src/components/CaptionsDialog.tsx +23 -0
- package/src/components/EditCaptionDialog.tsx +508 -0
- package/src/components/Onboard.tsx +2 -2
- package/src/components/PageSelector.tsx +57 -0
- package/src/components/PlayerActionsMenu.tsx +13 -5
- package/src/components/TextTracksManager.tsx +781 -0
- package/src/components/Uploader.styled.tsx +8 -15
- package/src/components/Uploader.tsx +7 -0
- package/src/components/VideoDetails/VideoDetails.tsx +16 -0
- package/src/components/VideoPlayer.tsx +2 -0
- package/src/components/VideosBrowser.tsx +9 -1
- package/src/hooks/useAccessControl.ts +1 -0
- package/src/hooks/useDialogState.ts +1 -1
- package/src/util/getVideoMetadata.ts +3 -1
- package/src/util/textTracks.ts +219 -0
- package/src/util/types.ts +12 -4
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useClient as useClient$1, createHookFromObservableFactory, useDocumentStore, collate, useDocumentValues, truncateString, useFormattedDuration, SanityDefaultPreview, useTimeAgo, TextWithTone, isRecord, getPreviewStateObservable, getPreviewValueWithFallback, DocumentPreviewPresence, useDocumentPreviewStore, useSchema, useDocumentPresence, PreviewCard, useCurrentUser, isReference, useProjectId, useDataset, PatchEvent, unset, setIfMissing, set, LinearProgress, FormField as FormField$2, definePlugin } from "sanity";
|
|
2
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { ErrorOutlineIcon, InfoOutlineIcon, RetryIcon, CheckmarkCircleIcon, RetrieveIcon, SyncIcon, SortIcon,
|
|
4
|
-
import { useTheme_v2, Stack, Flex, Box, Text, Button, Dialog, Card, TextInput, Checkbox, Code, Inline, Spinner, Heading, MenuButton, Menu, MenuItem,
|
|
3
|
+
import { ErrorOutlineIcon, InfoOutlineIcon, RetryIcon, CheckmarkCircleIcon, RetrieveIcon, ChevronLeftIcon, ChevronRightIcon, SyncIcon, SortIcon, UploadIcon, TranslateIcon, DownloadIcon, AddIcon, ChevronUpIcon, ChevronDownIcon, TrashIcon, EditIcon, WarningOutlineIcon, PublishIcon, DocumentIcon, RevertIcon, SearchIcon, ClockIcon, CropIcon, CalendarIcon, TagIcon, CheckmarkIcon, LockIcon, PlayIcon, PlugIcon, EllipsisHorizontalIcon, ImageIcon, ResetIcon, WarningFilledIcon, DocumentVideoIcon } from "@sanity/icons";
|
|
4
|
+
import { useTheme_v2, Stack, Flex, Box, Text, Button, Dialog, Card, TextInput, Checkbox, Code, Inline, Spinner, Heading, Label as Label$1, MenuButton, Menu, MenuItem, useToast, Autocomplete, Tooltip, TabList, Tab, TabPanel, Grid, useClickOutsideEvent, Popover, MenuDivider, Radio, rem } from "@sanity/ui";
|
|
5
5
|
import React, { useState, useMemo, useCallback, useReducer, useId, memo, useRef, useEffect, createContext, useContext, isValidElement, PureComponent, createElement, forwardRef, Suspense } from "react";
|
|
6
6
|
import compact from "lodash/compact.js";
|
|
7
7
|
import toLower from "lodash/toLower.js";
|
|
@@ -13,6 +13,7 @@ import { defer, timer, of, Observable, concat, throwError, from, Subject } from
|
|
|
13
13
|
import { styled, css } from "styled-components";
|
|
14
14
|
import { uuid } from "@sanity/uuid";
|
|
15
15
|
import { expand, concatMap, tap, switchMap, mergeMap, catchError, mergeMapTo, takeUntil } from "rxjs/operators";
|
|
16
|
+
import LanguagesList from "iso-639-1";
|
|
16
17
|
import MuxPlayer from "@mux/mux-player-react/lazy";
|
|
17
18
|
import { IntentLink } from "sanity/router";
|
|
18
19
|
import isNumber from "lodash/isNumber.js";
|
|
@@ -22,7 +23,6 @@ import useSWR from "swr";
|
|
|
22
23
|
import scrollIntoView from "scroll-into-view-if-needed";
|
|
23
24
|
import { UpChunk } from "@mux/upchunk";
|
|
24
25
|
import { isValidElementType } from "react-is";
|
|
25
|
-
import LanguagesList from "iso-639-1";
|
|
26
26
|
const ToolIcon = () => /* @__PURE__ */ jsx(
|
|
27
27
|
"svg",
|
|
28
28
|
{
|
|
@@ -567,6 +567,51 @@ function listAssets(client, options) {
|
|
|
567
567
|
query
|
|
568
568
|
});
|
|
569
569
|
}
|
|
570
|
+
function addTextTrackFromUrl(client, assetId, vttUrl, options) {
|
|
571
|
+
const { dataset } = client.config();
|
|
572
|
+
return client.request({
|
|
573
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks`,
|
|
574
|
+
withCredentials: !0,
|
|
575
|
+
method: "POST",
|
|
576
|
+
body: {
|
|
577
|
+
url: vttUrl,
|
|
578
|
+
type: "text",
|
|
579
|
+
language_code: options.language_code,
|
|
580
|
+
name: options.name,
|
|
581
|
+
text_type: options.text_type || "subtitles"
|
|
582
|
+
},
|
|
583
|
+
headers: {
|
|
584
|
+
"Content-Type": "application/json"
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
function generateSubtitles(client, assetId, audioTrackId, options) {
|
|
589
|
+
const { dataset } = client.config();
|
|
590
|
+
return client.request({
|
|
591
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks/${audioTrackId}/generate-subtitles`,
|
|
592
|
+
withCredentials: !0,
|
|
593
|
+
method: "POST",
|
|
594
|
+
body: {
|
|
595
|
+
generated_subtitles: [
|
|
596
|
+
{
|
|
597
|
+
language_code: options.language_code,
|
|
598
|
+
name: options.name
|
|
599
|
+
}
|
|
600
|
+
]
|
|
601
|
+
},
|
|
602
|
+
headers: {
|
|
603
|
+
"Content-Type": "application/json"
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
function deleteTextTrack(client, assetId, trackId) {
|
|
608
|
+
const { dataset } = client.config();
|
|
609
|
+
return client.request({
|
|
610
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks/${trackId}`,
|
|
611
|
+
withCredentials: !0,
|
|
612
|
+
method: "DELETE"
|
|
613
|
+
});
|
|
614
|
+
}
|
|
570
615
|
const ASSETS_PER_PAGE = 100;
|
|
571
616
|
async function fetchMuxAssetsPage(client, cursor) {
|
|
572
617
|
try {
|
|
@@ -1104,6 +1149,46 @@ function ImportVideosFromMux() {
|
|
|
1104
1149
|
if (importAssets.hasSecrets)
|
|
1105
1150
|
return importAssets.dialogOpen ? /* @__PURE__ */ jsx(ImportVideosDialog, { ...importAssets }) : /* @__PURE__ */ jsx(Button, { mode: "bleed", text: "Import from Mux", onClick: importAssets.openDialog });
|
|
1106
1151
|
}
|
|
1152
|
+
const PageSelector = (props) => {
|
|
1153
|
+
const page = props.page, setPage = props.setPage;
|
|
1154
|
+
return useEffect(() => {
|
|
1155
|
+
const clamped = Math.min(props.total - 1, Math.max(0, page));
|
|
1156
|
+
page !== clamped && setPage(clamped);
|
|
1157
|
+
}, [page, props.total, setPage]), /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1158
|
+
/* @__PURE__ */ jsx(
|
|
1159
|
+
Button,
|
|
1160
|
+
{
|
|
1161
|
+
icon: ChevronLeftIcon,
|
|
1162
|
+
mode: "bleed",
|
|
1163
|
+
padding: 3,
|
|
1164
|
+
style: { cursor: "pointer" },
|
|
1165
|
+
disabled: page <= 0,
|
|
1166
|
+
onClick: () => {
|
|
1167
|
+
setPage((page2) => Math.min(props.total - 1, Math.max(0, page2 - 1)));
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
),
|
|
1171
|
+
/* @__PURE__ */ jsxs(Label$1, { muted: !0, children: [
|
|
1172
|
+
"Page ",
|
|
1173
|
+
page + 1,
|
|
1174
|
+
"/",
|
|
1175
|
+
props.total
|
|
1176
|
+
] }),
|
|
1177
|
+
/* @__PURE__ */ jsx(
|
|
1178
|
+
Button,
|
|
1179
|
+
{
|
|
1180
|
+
icon: ChevronRightIcon,
|
|
1181
|
+
mode: "bleed",
|
|
1182
|
+
padding: 3,
|
|
1183
|
+
style: { cursor: "pointer" },
|
|
1184
|
+
disabled: page >= props.total - 1,
|
|
1185
|
+
onClick: () => {
|
|
1186
|
+
setPage((page2) => Math.min(props.total - 1, Math.max(0, page2 + 1)));
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
)
|
|
1190
|
+
] });
|
|
1191
|
+
};
|
|
1107
1192
|
function useResyncMuxMetadata() {
|
|
1108
1193
|
const documentStore = useDocumentStore(), client = useClient$1({
|
|
1109
1194
|
apiVersion: SANITY_API_VERSION
|
|
@@ -1329,55 +1414,1266 @@ function SelectSortOptions(props) {
|
|
|
1329
1414
|
}
|
|
1330
1415
|
);
|
|
1331
1416
|
}
|
|
1332
|
-
const SpinnerBox = () => /* @__PURE__ */ jsx(
|
|
1333
|
-
Box,
|
|
1334
|
-
{
|
|
1335
|
-
style: {
|
|
1336
|
-
display: "flex",
|
|
1337
|
-
alignItems: "center",
|
|
1338
|
-
justifyContent: "center",
|
|
1339
|
-
minHeight: "150px"
|
|
1340
|
-
},
|
|
1341
|
-
children: /* @__PURE__ */ jsx(Spinner, {})
|
|
1342
|
-
}
|
|
1343
|
-
), IconInfo = (props) => {
|
|
1344
|
-
const Icon = props.icon;
|
|
1345
|
-
return /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", padding: 1, children: [
|
|
1346
|
-
/* @__PURE__ */ jsx(Text, { size: (props.size || 1) + 1, muted: !0, children: /* @__PURE__ */ jsx(Icon, {}) }),
|
|
1347
|
-
/* @__PURE__ */ jsx(Text, { size: props.size || 1, muted: props.muted, children: props.text })
|
|
1417
|
+
const SpinnerBox = () => /* @__PURE__ */ jsx(
|
|
1418
|
+
Box,
|
|
1419
|
+
{
|
|
1420
|
+
style: {
|
|
1421
|
+
display: "flex",
|
|
1422
|
+
alignItems: "center",
|
|
1423
|
+
justifyContent: "center",
|
|
1424
|
+
minHeight: "150px"
|
|
1425
|
+
},
|
|
1426
|
+
children: /* @__PURE__ */ jsx(Spinner, {})
|
|
1427
|
+
}
|
|
1428
|
+
), IconInfo = (props) => {
|
|
1429
|
+
const Icon = props.icon;
|
|
1430
|
+
return /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", padding: 1, children: [
|
|
1431
|
+
/* @__PURE__ */ jsx(Text, { size: (props.size || 1) + 1, muted: !0, children: /* @__PURE__ */ jsx(Icon, {}) }),
|
|
1432
|
+
/* @__PURE__ */ jsx(Text, { size: props.size || 1, muted: props.muted, children: props.text })
|
|
1433
|
+
] });
|
|
1434
|
+
};
|
|
1435
|
+
function ResolutionIcon(props) {
|
|
1436
|
+
return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 24 24", ...props, children: /* @__PURE__ */ jsx(
|
|
1437
|
+
"path",
|
|
1438
|
+
{
|
|
1439
|
+
fill: "currentColor",
|
|
1440
|
+
d: "M20 9V6h-3V4h5v5h-2ZM2 9V4h5v2H4v3H2Zm15 11v-2h3v-3h2v5h-5ZM2 20v-5h2v3h3v2H2Zm4-4V8h12v8H6Zm2-2h8v-4H8v4Zm0 0v-4v4Z"
|
|
1441
|
+
}
|
|
1442
|
+
) });
|
|
1443
|
+
}
|
|
1444
|
+
function StopWatchIcon(props) {
|
|
1445
|
+
return /* @__PURE__ */ jsxs(
|
|
1446
|
+
"svg",
|
|
1447
|
+
{
|
|
1448
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1449
|
+
width: "1em",
|
|
1450
|
+
height: "1em",
|
|
1451
|
+
viewBox: "0 0 512 512",
|
|
1452
|
+
...props,
|
|
1453
|
+
children: [
|
|
1454
|
+
/* @__PURE__ */ jsx("path", { d: "M232 306.667h48V176h-48v130.667z", fill: "currentColor" }),
|
|
1455
|
+
/* @__PURE__ */ jsx(
|
|
1456
|
+
"path",
|
|
1457
|
+
{
|
|
1458
|
+
d: "M407.67 170.271l30.786-30.786-33.942-33.941-30.785 30.786C341.217 111.057 300.369 96 256 96 149.961 96 64 181.961 64 288s85.961 192 192 192 192-85.961 192-192c0-44.369-15.057-85.217-40.33-117.729zm-45.604 223.795C333.734 422.398 296.066 438 256 438s-77.735-15.602-106.066-43.934C121.602 365.735 106 328.066 106 288s15.602-77.735 43.934-106.066C178.265 153.602 215.934 138 256 138s77.734 15.602 106.066 43.934C390.398 210.265 406 247.934 406 288s-15.602 77.735-43.934 106.066z",
|
|
1459
|
+
fill: "currentColor"
|
|
1460
|
+
}
|
|
1461
|
+
),
|
|
1462
|
+
/* @__PURE__ */ jsx("path", { d: "M192 32h128v48H192z", fill: "currentColor" })
|
|
1463
|
+
]
|
|
1464
|
+
}
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
function extractErrorMessage(error, defaultMessage = "Failed to process request") {
|
|
1468
|
+
let message = "";
|
|
1469
|
+
if (error && typeof error == "object") {
|
|
1470
|
+
const err = error;
|
|
1471
|
+
message = err.response?.body?.message || err.message || "";
|
|
1472
|
+
} else typeof error == "string" && (message = error);
|
|
1473
|
+
if (!message)
|
|
1474
|
+
return defaultMessage;
|
|
1475
|
+
const match = message.match(/\(([^)]+)\)/);
|
|
1476
|
+
if (match && match[1])
|
|
1477
|
+
return match[1];
|
|
1478
|
+
if (message.includes("responded with")) {
|
|
1479
|
+
const parts = message.split("(");
|
|
1480
|
+
if (parts.length > 1)
|
|
1481
|
+
return parts[parts.length - 1].replace(")", "").trim();
|
|
1482
|
+
}
|
|
1483
|
+
return message;
|
|
1484
|
+
}
|
|
1485
|
+
async function pollTrackStatus(options) {
|
|
1486
|
+
const {
|
|
1487
|
+
client,
|
|
1488
|
+
assetId,
|
|
1489
|
+
trackName,
|
|
1490
|
+
trackLanguageCode,
|
|
1491
|
+
maxAttempts = 10,
|
|
1492
|
+
onTrackFound,
|
|
1493
|
+
onTrackErrored,
|
|
1494
|
+
onTrackReady
|
|
1495
|
+
} = options, trimmedName = trackName.trim(), trimmedLanguageCode = trackLanguageCode.trim();
|
|
1496
|
+
let newTrack, attempts = 0, trackFound = !1;
|
|
1497
|
+
const findTrack = (textTracks) => {
|
|
1498
|
+
let foundTrack = textTracks.find(
|
|
1499
|
+
(track) => track.name === trimmedName && track.language_code === trimmedLanguageCode
|
|
1500
|
+
);
|
|
1501
|
+
return foundTrack || (foundTrack = textTracks.find((track) => track.language_code === trimmedLanguageCode)), !foundTrack && textTracks.length > 0 && (foundTrack = textTracks[textTracks.length - 1]), foundTrack;
|
|
1502
|
+
};
|
|
1503
|
+
for (; attempts < maxAttempts; ) {
|
|
1504
|
+
try {
|
|
1505
|
+
attempts > 0 && await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1506
|
+
const textTracks = (await getAsset(client, assetId)).data.tracks?.filter((track) => track.type === "text") || [], foundTrack = findTrack(textTracks);
|
|
1507
|
+
if (!foundTrack) {
|
|
1508
|
+
attempts++;
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
if (trackFound = !0, newTrack = foundTrack, onTrackFound && onTrackFound(foundTrack), foundTrack.status === "ready") {
|
|
1512
|
+
onTrackReady && onTrackReady(foundTrack);
|
|
1513
|
+
break;
|
|
1514
|
+
}
|
|
1515
|
+
if (foundTrack.status === "errored")
|
|
1516
|
+
return onTrackErrored && onTrackErrored(foundTrack), {
|
|
1517
|
+
track: foundTrack,
|
|
1518
|
+
found: !0,
|
|
1519
|
+
status: "errored"
|
|
1520
|
+
};
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
console.error("Failed to fetch updated asset:", error);
|
|
1523
|
+
}
|
|
1524
|
+
attempts++;
|
|
1525
|
+
}
|
|
1526
|
+
return !newTrack || !trackFound ? {
|
|
1527
|
+
track: void 0,
|
|
1528
|
+
found: !1,
|
|
1529
|
+
status: "not-found"
|
|
1530
|
+
} : newTrack.status === "preparing" ? {
|
|
1531
|
+
track: newTrack,
|
|
1532
|
+
found: !0,
|
|
1533
|
+
status: "preparing"
|
|
1534
|
+
} : {
|
|
1535
|
+
track: newTrack,
|
|
1536
|
+
found: !0,
|
|
1537
|
+
status: "ready"
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
async function downloadVttFile(client, asset, track) {
|
|
1541
|
+
if (!track.id)
|
|
1542
|
+
throw new Error("Track ID is missing");
|
|
1543
|
+
if (track.status !== "ready")
|
|
1544
|
+
throw new Error(`Track is not ready yet. Status: ${track.status}`);
|
|
1545
|
+
if (!asset.assetId)
|
|
1546
|
+
throw new Error("Asset ID is required");
|
|
1547
|
+
const playbackId = getPlaybackId(asset);
|
|
1548
|
+
if (!playbackId)
|
|
1549
|
+
throw new Error("Playback ID is required");
|
|
1550
|
+
const playbackPolicy = getPlaybackPolicy(asset);
|
|
1551
|
+
let downloadUrl = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
|
|
1552
|
+
if (playbackPolicy === "signed") {
|
|
1553
|
+
const token = generateJwt(client, playbackId, "v");
|
|
1554
|
+
downloadUrl += `?token=${token}`;
|
|
1555
|
+
}
|
|
1556
|
+
const response = await fetch(downloadUrl);
|
|
1557
|
+
if (!response.ok)
|
|
1558
|
+
throw new Error(`Failed to download file: ${response.statusText}`);
|
|
1559
|
+
const blob = await response.blob(), blobUrl = URL.createObjectURL(blob), link = document.createElement("a");
|
|
1560
|
+
link.href = blobUrl, link.download = `${asset.filename || "captions"}-${track.language_code || "en"}.vtt`, document.body.appendChild(link), link.click(), document.body.removeChild(link), URL.revokeObjectURL(blobUrl);
|
|
1561
|
+
}
|
|
1562
|
+
const SUPPORTED_MUX_LANGUAGES = [
|
|
1563
|
+
{ label: "English", code: "en", state: "Stable" },
|
|
1564
|
+
{ label: "Spanish", code: "es", state: "Stable" },
|
|
1565
|
+
{ label: "Italian", code: "it", state: "Stable" },
|
|
1566
|
+
{ label: "Portuguese", code: "pt", state: "Stable" },
|
|
1567
|
+
{ label: "German", code: "de", state: "Stable" },
|
|
1568
|
+
{ label: "French", code: "fr", state: "Stable" },
|
|
1569
|
+
{ label: "Polish", code: "pl", state: "Beta" },
|
|
1570
|
+
{ label: "Russian", code: "ru", state: "Beta" },
|
|
1571
|
+
{ label: "Dutch", code: "nl", state: "Beta" },
|
|
1572
|
+
{ label: "Catalan", code: "ca", state: "Beta" },
|
|
1573
|
+
{ label: "Turkish", code: "tr", state: "Beta" },
|
|
1574
|
+
{ label: "Swedish", code: "sv", state: "Beta" },
|
|
1575
|
+
{ label: "Ukrainian", code: "uk", state: "Beta" },
|
|
1576
|
+
{ label: "Norwegian", code: "no", state: "Beta" },
|
|
1577
|
+
{ label: "Finnish", code: "fi", state: "Beta" },
|
|
1578
|
+
{ label: "Slovak", code: "sk", state: "Beta" },
|
|
1579
|
+
{ label: "Greek", code: "el", state: "Beta" },
|
|
1580
|
+
{ label: "Czech", code: "cs", state: "Beta" },
|
|
1581
|
+
{ label: "Croatian", code: "hr", state: "Beta" },
|
|
1582
|
+
{ label: "Danish", code: "da", state: "Beta" },
|
|
1583
|
+
{ label: "Romanian", code: "ro", state: "Beta" },
|
|
1584
|
+
{ label: "Bulgarian", code: "bg", state: "Beta" }
|
|
1585
|
+
];
|
|
1586
|
+
function isCustomTextTrack(track) {
|
|
1587
|
+
return track.type !== "autogenerated";
|
|
1588
|
+
}
|
|
1589
|
+
function isAutogeneratedTrack(track) {
|
|
1590
|
+
return track.type === "autogenerated";
|
|
1591
|
+
}
|
|
1592
|
+
const LANGUAGE_OPTIONS$1 = LanguagesList.getAllCodes().map((code) => ({
|
|
1593
|
+
value: code,
|
|
1594
|
+
label: LanguagesList.getNativeName(code)
|
|
1595
|
+
})), MUX_LANGUAGE_OPTIONS = SUPPORTED_MUX_LANGUAGES.map((lang) => ({
|
|
1596
|
+
value: lang.code,
|
|
1597
|
+
label: lang.label
|
|
1598
|
+
}));
|
|
1599
|
+
function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
1600
|
+
const client = useClient(), toast = useToast(), dialogId = `AddCaptionDialog${useId()}`, [isAutogenerated, setIsAutogenerated] = useState(!1), [vttUrl, setVttUrl] = useState(""), [languageCode, setLanguageCode] = useState(""), [selectedLanguage, setSelectedLanguage] = useState(
|
|
1601
|
+
null
|
|
1602
|
+
), [name2, setName] = useState(""), [isSubmitting, setIsSubmitting] = useState(!1), [selectedFile, setSelectedFile] = useState(null), fileInputRef = useRef(null), uploadVttFile = async (file) => (await client.assets.upload("file", file, {
|
|
1603
|
+
filename: file.name
|
|
1604
|
+
})).url, handleAddTrackFromUrl = async () => {
|
|
1605
|
+
if (!asset.assetId)
|
|
1606
|
+
throw new Error("Asset ID is required");
|
|
1607
|
+
const trimmedName = name2.trim(), trimmedLanguageCode = languageCode.trim();
|
|
1608
|
+
let vttUrlToUse = vttUrl.trim();
|
|
1609
|
+
if (selectedFile)
|
|
1610
|
+
try {
|
|
1611
|
+
vttUrlToUse = await uploadVttFile(selectedFile);
|
|
1612
|
+
} catch (uploadError) {
|
|
1613
|
+
throw toast.push({
|
|
1614
|
+
title: "Failed to upload VTT file",
|
|
1615
|
+
status: "error",
|
|
1616
|
+
description: "Could not upload the VTT file to Sanity. Please try again."
|
|
1617
|
+
}), setIsSubmitting(!1), uploadError;
|
|
1618
|
+
}
|
|
1619
|
+
await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
|
|
1620
|
+
language_code: trimmedLanguageCode,
|
|
1621
|
+
name: trimmedName,
|
|
1622
|
+
text_type: "subtitles"
|
|
1623
|
+
});
|
|
1624
|
+
const result = await pollTrackStatus({
|
|
1625
|
+
client,
|
|
1626
|
+
assetId: asset.assetId,
|
|
1627
|
+
trackName: trimmedName,
|
|
1628
|
+
trackLanguageCode: trimmedLanguageCode,
|
|
1629
|
+
onTrackErrored: (track) => {
|
|
1630
|
+
const errorMessage = track.error?.messages?.[0] || track.error?.type || "The track failed to download from the provided URL";
|
|
1631
|
+
toast.push({
|
|
1632
|
+
title: "Caption track failed",
|
|
1633
|
+
status: "error",
|
|
1634
|
+
description: errorMessage
|
|
1635
|
+
}), onAdd(track), onClose();
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
if (!result.found || !result.track) {
|
|
1639
|
+
toast.push({
|
|
1640
|
+
title: "Caption track may have been added",
|
|
1641
|
+
status: "warning",
|
|
1642
|
+
description: "The track was created but its status could not be determined. It may still be processing. Please refresh the page to see if it appears."
|
|
1643
|
+
}), onClose();
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
if (result.status !== "errored") {
|
|
1647
|
+
if (result.status === "preparing") {
|
|
1648
|
+
toast.push({
|
|
1649
|
+
title: "Caption track is processing",
|
|
1650
|
+
status: "info",
|
|
1651
|
+
description: "The track was created and is being processed. It will appear in the list shortly."
|
|
1652
|
+
}), onAdd(result.track), onClose();
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
toast.push({
|
|
1656
|
+
title: "Caption track added",
|
|
1657
|
+
status: "success",
|
|
1658
|
+
description: "Caption track added successfully"
|
|
1659
|
+
}), onAdd(result.track), onClose();
|
|
1660
|
+
}
|
|
1661
|
+
}, handleGenerateSubtitles = async () => {
|
|
1662
|
+
if (!asset.assetId)
|
|
1663
|
+
throw new Error("Asset ID is required");
|
|
1664
|
+
const audioTrack = (await getAsset(client, asset.assetId)).data.tracks?.find((track) => track.type === "audio");
|
|
1665
|
+
if (!audioTrack || !audioTrack.id)
|
|
1666
|
+
throw toast.push({
|
|
1667
|
+
title: "No audio track found",
|
|
1668
|
+
status: "error",
|
|
1669
|
+
description: "The asset does not have an audio track. Auto-generated subtitles require an audio track."
|
|
1670
|
+
}), new Error("No audio track found");
|
|
1671
|
+
await generateSubtitles(client, asset.assetId, audioTrack.id, {
|
|
1672
|
+
language_code: languageCode.trim(),
|
|
1673
|
+
name: name2.trim()
|
|
1674
|
+
});
|
|
1675
|
+
const mockTrack = {
|
|
1676
|
+
type: "text",
|
|
1677
|
+
id: `generating-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
1678
|
+
text_type: "subtitles",
|
|
1679
|
+
text_source: "generated_live",
|
|
1680
|
+
language_code: languageCode.trim(),
|
|
1681
|
+
name: name2.trim(),
|
|
1682
|
+
status: "preparing"
|
|
1683
|
+
};
|
|
1684
|
+
toast.push({
|
|
1685
|
+
title: "Generating subtitles",
|
|
1686
|
+
status: "success",
|
|
1687
|
+
description: "This may take a few minutes"
|
|
1688
|
+
}), onAdd(mockTrack), onClose();
|
|
1689
|
+
}, handleSubmit = async () => {
|
|
1690
|
+
if (!isAutogenerated) {
|
|
1691
|
+
if (!selectedFile && !vttUrl.trim()) {
|
|
1692
|
+
toast.push({
|
|
1693
|
+
title: "VTT file or URL required",
|
|
1694
|
+
status: "error",
|
|
1695
|
+
description: "Please select a VTT file or enter a VTT file URL"
|
|
1696
|
+
});
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
if (vttUrl.trim() && !selectedFile)
|
|
1700
|
+
try {
|
|
1701
|
+
new URL(vttUrl.trim());
|
|
1702
|
+
} catch {
|
|
1703
|
+
toast.push({
|
|
1704
|
+
title: "Invalid URL",
|
|
1705
|
+
status: "error",
|
|
1706
|
+
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt)"
|
|
1707
|
+
});
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
if (!name2.trim()) {
|
|
1712
|
+
toast.push({
|
|
1713
|
+
title: "Audio name required",
|
|
1714
|
+
status: "error",
|
|
1715
|
+
description: "Please enter an audio name for this caption track"
|
|
1716
|
+
});
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
if (!languageCode.trim()) {
|
|
1720
|
+
toast.push({
|
|
1721
|
+
title: "Language code required",
|
|
1722
|
+
status: "error",
|
|
1723
|
+
description: "Please enter a language code (e.g., en, es, fr)"
|
|
1724
|
+
});
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
setIsSubmitting(!0);
|
|
1728
|
+
try {
|
|
1729
|
+
isAutogenerated ? await handleGenerateSubtitles() : await handleAddTrackFromUrl();
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
toast.push({
|
|
1732
|
+
title: "Failed to add caption track",
|
|
1733
|
+
status: "error",
|
|
1734
|
+
description: extractErrorMessage(error, "Failed to add caption track")
|
|
1735
|
+
});
|
|
1736
|
+
} finally {
|
|
1737
|
+
setIsSubmitting(!1);
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
return /* @__PURE__ */ jsx(
|
|
1741
|
+
Dialog,
|
|
1742
|
+
{
|
|
1743
|
+
id: dialogId,
|
|
1744
|
+
header: "Add Caption Track",
|
|
1745
|
+
onClose,
|
|
1746
|
+
width: 1,
|
|
1747
|
+
onClickOutside: onClose,
|
|
1748
|
+
children: /* @__PURE__ */ jsxs(Stack, { padding: 4, space: 4, children: [
|
|
1749
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1750
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", marginBottom: 3, children: [
|
|
1751
|
+
/* @__PURE__ */ jsx(
|
|
1752
|
+
Checkbox,
|
|
1753
|
+
{
|
|
1754
|
+
id: "autogenerated-checkbox",
|
|
1755
|
+
style: { display: "block" },
|
|
1756
|
+
checked: isAutogenerated,
|
|
1757
|
+
onChange: (e) => {
|
|
1758
|
+
setIsAutogenerated(e.currentTarget.checked), e.currentTarget.checked && setVttUrl("");
|
|
1759
|
+
},
|
|
1760
|
+
disabled: isSubmitting
|
|
1761
|
+
}
|
|
1762
|
+
),
|
|
1763
|
+
/* @__PURE__ */ jsx(Flex, { flex: 1, paddingLeft: 2, children: /* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsx("label", { htmlFor: "autogenerated-checkbox", children: "Generate captions" }) }) })
|
|
1764
|
+
] }),
|
|
1765
|
+
!isAutogenerated && /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1766
|
+
/* @__PURE__ */ jsxs(Card, { padding: 3, marginBottom: 2, tone: "transparent", border: !0, radius: 2, children: [
|
|
1767
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", children: [
|
|
1768
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: selectedFile ? `Selected: ${selectedFile.name}` : "No file selected" }),
|
|
1769
|
+
/* @__PURE__ */ jsx(
|
|
1770
|
+
Button,
|
|
1771
|
+
{
|
|
1772
|
+
icon: UploadIcon,
|
|
1773
|
+
text: "Select File",
|
|
1774
|
+
mode: "ghost",
|
|
1775
|
+
tone: "primary",
|
|
1776
|
+
fontSize: 1,
|
|
1777
|
+
padding: 2,
|
|
1778
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1779
|
+
disabled: isSubmitting
|
|
1780
|
+
}
|
|
1781
|
+
)
|
|
1782
|
+
] }),
|
|
1783
|
+
/* @__PURE__ */ jsx(
|
|
1784
|
+
"input",
|
|
1785
|
+
{
|
|
1786
|
+
ref: fileInputRef,
|
|
1787
|
+
type: "file",
|
|
1788
|
+
accept: ".vtt,text/vtt",
|
|
1789
|
+
style: { display: "none" },
|
|
1790
|
+
onChange: (e) => {
|
|
1791
|
+
e.target.files && e.target.files.length > 0 && !isSubmitting && (setSelectedFile(e.target.files[0]), setVttUrl(""));
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
)
|
|
1795
|
+
] }),
|
|
1796
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, style: { textAlign: "center" }, children: "Or enter the VTT file URL" }),
|
|
1797
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1798
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "vtt-url", children: "VTT File URL" }),
|
|
1799
|
+
/* @__PURE__ */ jsx(
|
|
1800
|
+
TextInput,
|
|
1801
|
+
{
|
|
1802
|
+
id: "vtt-url",
|
|
1803
|
+
placeholder: "https://example.com/subtitles.vtt",
|
|
1804
|
+
value: vttUrl,
|
|
1805
|
+
onChange: (e) => {
|
|
1806
|
+
setVttUrl(e.currentTarget.value), setSelectedFile(null);
|
|
1807
|
+
},
|
|
1808
|
+
disabled: isSubmitting
|
|
1809
|
+
}
|
|
1810
|
+
)
|
|
1811
|
+
] })
|
|
1812
|
+
] })
|
|
1813
|
+
] }),
|
|
1814
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1815
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-name", children: "Audio name" }),
|
|
1816
|
+
/* @__PURE__ */ jsx(
|
|
1817
|
+
Autocomplete,
|
|
1818
|
+
{
|
|
1819
|
+
id: "caption-name",
|
|
1820
|
+
value: selectedLanguage?.value || "",
|
|
1821
|
+
onChange: (newValue) => {
|
|
1822
|
+
const selected = (isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1).find((opt) => opt.value === newValue);
|
|
1823
|
+
selected && (setSelectedLanguage(selected), setLanguageCode(selected.value), setName(selected.label));
|
|
1824
|
+
},
|
|
1825
|
+
options: isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1,
|
|
1826
|
+
icon: TranslateIcon,
|
|
1827
|
+
placeholder: "Select language",
|
|
1828
|
+
filterOption: (query, option) => option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 || option.value.toLowerCase().indexOf(query.toLowerCase()) > -1,
|
|
1829
|
+
openButton: !0,
|
|
1830
|
+
renderValue: (value) => (isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1).find(
|
|
1831
|
+
(l) => l.value === value
|
|
1832
|
+
)?.label || value,
|
|
1833
|
+
renderOption: (option) => /* @__PURE__ */ jsx(Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: /* @__PURE__ */ jsxs(Text, { size: 2, textOverflow: "ellipsis", children: [
|
|
1834
|
+
option.label,
|
|
1835
|
+
" (",
|
|
1836
|
+
option.value,
|
|
1837
|
+
")"
|
|
1838
|
+
] }) }),
|
|
1839
|
+
disabled: isSubmitting
|
|
1840
|
+
}
|
|
1841
|
+
)
|
|
1842
|
+
] }),
|
|
1843
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1844
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-language", children: "Language Code" }),
|
|
1845
|
+
/* @__PURE__ */ jsx(
|
|
1846
|
+
TextInput,
|
|
1847
|
+
{
|
|
1848
|
+
id: "caption-language",
|
|
1849
|
+
placeholder: "en-US",
|
|
1850
|
+
value: languageCode,
|
|
1851
|
+
onChange: (e) => {
|
|
1852
|
+
setLanguageCode(e.currentTarget.value), selectedLanguage && selectedLanguage.value !== e.currentTarget.value && (setSelectedLanguage(null), (!name2 || name2 === selectedLanguage.label) && setName(""));
|
|
1853
|
+
},
|
|
1854
|
+
disabled: isSubmitting
|
|
1855
|
+
}
|
|
1856
|
+
)
|
|
1857
|
+
] }),
|
|
1858
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, justify: "flex-end", marginTop: 2, children: [
|
|
1859
|
+
/* @__PURE__ */ jsx(Button, { text: "Cancel", mode: "ghost", onClick: onClose, disabled: isSubmitting }),
|
|
1860
|
+
/* @__PURE__ */ jsx(
|
|
1861
|
+
Button,
|
|
1862
|
+
{
|
|
1863
|
+
text: "Add Caption Track",
|
|
1864
|
+
tone: "primary",
|
|
1865
|
+
icon: isSubmitting ? /* @__PURE__ */ jsx(
|
|
1866
|
+
Spinner,
|
|
1867
|
+
{
|
|
1868
|
+
style: {
|
|
1869
|
+
verticalAlign: "middle",
|
|
1870
|
+
display: "inline-block",
|
|
1871
|
+
marginBottom: "-3px",
|
|
1872
|
+
width: "1em",
|
|
1873
|
+
height: "1em",
|
|
1874
|
+
marginRight: "-6px"
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
) : /* @__PURE__ */ jsx(UploadIcon, {}),
|
|
1878
|
+
onClick: handleSubmit,
|
|
1879
|
+
disabled: isSubmitting
|
|
1880
|
+
}
|
|
1881
|
+
)
|
|
1882
|
+
] })
|
|
1883
|
+
] })
|
|
1884
|
+
}
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
const LANGUAGE_OPTIONS = LanguagesList.getAllCodes().map((code) => ({
|
|
1888
|
+
value: code,
|
|
1889
|
+
label: LanguagesList.getNativeName(code)
|
|
1890
|
+
}));
|
|
1891
|
+
function EditCaptionDialog({ asset, track, onUpdate, onClose }) {
|
|
1892
|
+
const client = useClient(), toast = useToast(), dialogId = `EditCaptionDialog${useId()}`, isAutogenerated = track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod", [vttUrl, setVttUrl] = useState(""), [languageCode, setLanguageCode] = useState(track.language_code || ""), [selectedLanguage, setSelectedLanguage] = useState(
|
|
1893
|
+
() => {
|
|
1894
|
+
const baseCode = track.language_code?.split("-")[0], found = LANGUAGE_OPTIONS.find(
|
|
1895
|
+
(opt) => opt.value === track.language_code || opt.value === baseCode
|
|
1896
|
+
);
|
|
1897
|
+
if (found) return found;
|
|
1898
|
+
if (track.name) {
|
|
1899
|
+
const foundByName = LANGUAGE_OPTIONS.find((opt) => opt.label === track.name);
|
|
1900
|
+
if (foundByName) return foundByName;
|
|
1901
|
+
}
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
), [name2, setName] = useState(track.name || ""), [isSubmitting, setIsSubmitting] = useState(!1), [downloading, setDownloading] = useState(!1), [selectedFile, setSelectedFile] = useState(null), fileInputRef = useRef(null);
|
|
1905
|
+
useEffect(() => {
|
|
1906
|
+
setLanguageCode(track.language_code || ""), setName(track.name || ""), setVttUrl("");
|
|
1907
|
+
const baseCode = track.language_code?.split("-")[0], foundByCode = LANGUAGE_OPTIONS.find(
|
|
1908
|
+
(opt) => opt.value === track.language_code || opt.value === baseCode
|
|
1909
|
+
), foundByName = track.name ? LANGUAGE_OPTIONS.find((opt) => opt.label === track.name) : null;
|
|
1910
|
+
setSelectedLanguage(foundByCode || foundByName || null);
|
|
1911
|
+
}, [track, asset, client]);
|
|
1912
|
+
const handleDownloadCurrentFile = async () => {
|
|
1913
|
+
setDownloading(!0);
|
|
1914
|
+
try {
|
|
1915
|
+
await downloadVttFile(client, asset, track);
|
|
1916
|
+
} catch (error) {
|
|
1917
|
+
let errorMessage = "Please try again", title = "Failed to download VTT file";
|
|
1918
|
+
error instanceof Error ? (errorMessage = error.message, error.message.includes("Track") && (title = "Cannot download")) : (error === "Track ID is missing" || error === "Track is not ready yet") && (errorMessage = String(error), title = "Cannot download"), toast.push({
|
|
1919
|
+
title,
|
|
1920
|
+
status: "error",
|
|
1921
|
+
description: errorMessage
|
|
1922
|
+
});
|
|
1923
|
+
} finally {
|
|
1924
|
+
setDownloading(!1);
|
|
1925
|
+
}
|
|
1926
|
+
}, getCurrentFileName = () => track.id && asset.filename ? `${asset.filename}-${track.language_code || "en"}.vtt` : `captions-${track.language_code || "en"}.vtt`, uploadVttFile = async (file) => (await client.assets.upload("file", file, {
|
|
1927
|
+
filename: file.name
|
|
1928
|
+
})).url, refreshAssetData = async () => {
|
|
1929
|
+
if (!(!asset._id || !asset.assetId))
|
|
1930
|
+
try {
|
|
1931
|
+
const latestAssetData = await getAsset(client, asset.assetId);
|
|
1932
|
+
await client.patch(asset._id).set({ data: latestAssetData.data, status: latestAssetData.data.status }).commit();
|
|
1933
|
+
} catch (refreshError) {
|
|
1934
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
1935
|
+
}
|
|
1936
|
+
}, handleUpdateTrackWithNewUrl = async () => {
|
|
1937
|
+
if (!asset.assetId)
|
|
1938
|
+
throw new Error("Asset ID is required");
|
|
1939
|
+
const trimmedName = name2.trim(), trimmedLanguageCode = languageCode.trim(), oldTrackId = track.id;
|
|
1940
|
+
try {
|
|
1941
|
+
await deleteTextTrack(client, asset.assetId, oldTrackId);
|
|
1942
|
+
} catch (deleteError) {
|
|
1943
|
+
throw toast.push({
|
|
1944
|
+
title: "Failed to delete old track",
|
|
1945
|
+
status: "error",
|
|
1946
|
+
description: "Could not delete the old track. Please try again or delete it manually."
|
|
1947
|
+
}), setIsSubmitting(!1), deleteError;
|
|
1948
|
+
}
|
|
1949
|
+
let vttUrlToUse = vttUrl.trim();
|
|
1950
|
+
if (selectedFile)
|
|
1951
|
+
try {
|
|
1952
|
+
vttUrlToUse = await uploadVttFile(selectedFile);
|
|
1953
|
+
} catch (uploadError) {
|
|
1954
|
+
throw toast.push({
|
|
1955
|
+
title: "Failed to upload VTT file",
|
|
1956
|
+
status: "error",
|
|
1957
|
+
description: "Could not upload the VTT file to Sanity. Please try again."
|
|
1958
|
+
}), setIsSubmitting(!1), uploadError;
|
|
1959
|
+
}
|
|
1960
|
+
try {
|
|
1961
|
+
await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
|
|
1962
|
+
language_code: trimmedLanguageCode,
|
|
1963
|
+
name: trimmedName,
|
|
1964
|
+
text_type: "subtitles"
|
|
1965
|
+
});
|
|
1966
|
+
} catch (error) {
|
|
1967
|
+
throw toast.push({
|
|
1968
|
+
title: "Failed to update caption track",
|
|
1969
|
+
status: "error",
|
|
1970
|
+
description: extractErrorMessage(error, "Failed to update caption track")
|
|
1971
|
+
}), setIsSubmitting(!1), error;
|
|
1972
|
+
}
|
|
1973
|
+
const result = await pollTrackStatus({
|
|
1974
|
+
client,
|
|
1975
|
+
assetId: asset.assetId,
|
|
1976
|
+
trackName: trimmedName,
|
|
1977
|
+
trackLanguageCode: trimmedLanguageCode,
|
|
1978
|
+
onTrackErrored: async (erroredTrack) => {
|
|
1979
|
+
const errorMessage = erroredTrack.error?.messages?.[0] || erroredTrack.error?.type || "The track failed to download from the provided URL";
|
|
1980
|
+
toast.push({
|
|
1981
|
+
title: "Caption track failed",
|
|
1982
|
+
status: "error",
|
|
1983
|
+
description: errorMessage
|
|
1984
|
+
}), await refreshAssetData(), onUpdate(erroredTrack, oldTrackId), setIsSubmitting(!1);
|
|
1985
|
+
}
|
|
1986
|
+
});
|
|
1987
|
+
if (!result.found || !result.track) {
|
|
1988
|
+
toast.push({
|
|
1989
|
+
title: "Caption track may have been updated",
|
|
1990
|
+
status: "warning",
|
|
1991
|
+
description: "The track was updated but its status could not be determined. It may still be processing. Please refresh the page to see if it appears."
|
|
1992
|
+
}), setIsSubmitting(!1);
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
result.status !== "errored" && (await refreshAssetData(), result.status === "preparing" ? toast.push({
|
|
1996
|
+
title: "Caption track is processing",
|
|
1997
|
+
status: "info",
|
|
1998
|
+
description: "The track was updated and is being processed. It will appear in the list shortly."
|
|
1999
|
+
}) : toast.push({
|
|
2000
|
+
title: "Caption track updated",
|
|
2001
|
+
status: "success",
|
|
2002
|
+
description: "Caption track updated successfully"
|
|
2003
|
+
}), onUpdate(result.track, oldTrackId), setIsSubmitting(!1));
|
|
2004
|
+
}, handleSubmit = async () => {
|
|
2005
|
+
if (!name2.trim()) {
|
|
2006
|
+
toast.push({
|
|
2007
|
+
title: "Audio name required",
|
|
2008
|
+
status: "error",
|
|
2009
|
+
description: "Please enter an audio name for this caption track"
|
|
2010
|
+
});
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
if (!languageCode.trim()) {
|
|
2014
|
+
toast.push({
|
|
2015
|
+
title: "Language code required",
|
|
2016
|
+
status: "error",
|
|
2017
|
+
description: "Please enter a language code (e.g., en, es, fr)"
|
|
2018
|
+
});
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
setIsSubmitting(!0);
|
|
2022
|
+
try {
|
|
2023
|
+
if (!asset.assetId)
|
|
2024
|
+
throw new Error("Asset ID is required");
|
|
2025
|
+
const originalVttUrl = (() => {
|
|
2026
|
+
if (isAutogenerated || !track.id) return "";
|
|
2027
|
+
const playbackId = getPlaybackId(asset);
|
|
2028
|
+
if (!playbackId) return "";
|
|
2029
|
+
let url = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
|
|
2030
|
+
if (getPlaybackPolicy(asset) === "signed") {
|
|
2031
|
+
const token = generateJwt(client, playbackId, "v");
|
|
2032
|
+
url += `?token=${token}`;
|
|
2033
|
+
}
|
|
2034
|
+
return url;
|
|
2035
|
+
})(), urlChanged = selectedFile !== null || vttUrl.trim() && vttUrl.trim() !== originalVttUrl;
|
|
2036
|
+
if (!urlChanged) {
|
|
2037
|
+
toast.push({
|
|
2038
|
+
title: "No changes",
|
|
2039
|
+
status: "info",
|
|
2040
|
+
description: 'Please provide a new VTT file or URL using the "Replace" button or URL field to update the track.'
|
|
2041
|
+
}), setIsSubmitting(!1);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
if (urlChanged) {
|
|
2045
|
+
if (!selectedFile && vttUrl.trim())
|
|
2046
|
+
try {
|
|
2047
|
+
new URL(vttUrl.trim());
|
|
2048
|
+
} catch {
|
|
2049
|
+
toast.push({
|
|
2050
|
+
title: "Invalid URL",
|
|
2051
|
+
status: "error",
|
|
2052
|
+
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt)"
|
|
2053
|
+
}), setIsSubmitting(!1);
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (!selectedFile && !vttUrl.trim()) {
|
|
2057
|
+
toast.push({
|
|
2058
|
+
title: "VTT file or URL required",
|
|
2059
|
+
status: "error",
|
|
2060
|
+
description: "Please select a VTT file or enter a VTT file URL"
|
|
2061
|
+
}), setIsSubmitting(!1);
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
await handleUpdateTrackWithNewUrl();
|
|
2065
|
+
}
|
|
2066
|
+
onClose();
|
|
2067
|
+
} catch (error) {
|
|
2068
|
+
toast.push({
|
|
2069
|
+
title: "Failed to update caption track",
|
|
2070
|
+
status: "error",
|
|
2071
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2072
|
+
});
|
|
2073
|
+
} finally {
|
|
2074
|
+
setIsSubmitting(!1);
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
return /* @__PURE__ */ jsx(
|
|
2078
|
+
Dialog,
|
|
2079
|
+
{
|
|
2080
|
+
id: dialogId,
|
|
2081
|
+
header: "Edit Caption Track",
|
|
2082
|
+
onClose,
|
|
2083
|
+
width: 1,
|
|
2084
|
+
onClickOutside: onClose,
|
|
2085
|
+
children: /* @__PURE__ */ jsxs(Stack, { padding: 4, space: 4, children: [
|
|
2086
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2087
|
+
/* @__PURE__ */ jsxs(Card, { padding: 3, marginBottom: 2, tone: "transparent", border: !0, radius: 2, children: [
|
|
2088
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", children: [
|
|
2089
|
+
/* @__PURE__ */ jsx(Text, { children: getCurrentFileName() }),
|
|
2090
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
2091
|
+
track.status !== "errored" && /* @__PURE__ */ jsx(
|
|
2092
|
+
Button,
|
|
2093
|
+
{
|
|
2094
|
+
icon: downloading ? /* @__PURE__ */ jsx(
|
|
2095
|
+
Spinner,
|
|
2096
|
+
{
|
|
2097
|
+
style: {
|
|
2098
|
+
verticalAlign: "middle",
|
|
2099
|
+
display: "inline-block",
|
|
2100
|
+
marginTop: "-2px",
|
|
2101
|
+
width: "0.5em",
|
|
2102
|
+
height: "0.5em"
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
) : /* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
2106
|
+
text: "Download",
|
|
2107
|
+
mode: "ghost",
|
|
2108
|
+
tone: "primary",
|
|
2109
|
+
fontSize: 1,
|
|
2110
|
+
padding: 2,
|
|
2111
|
+
onClick: handleDownloadCurrentFile,
|
|
2112
|
+
disabled: downloading || isSubmitting
|
|
2113
|
+
}
|
|
2114
|
+
),
|
|
2115
|
+
/* @__PURE__ */ jsx(
|
|
2116
|
+
Button,
|
|
2117
|
+
{
|
|
2118
|
+
icon: UploadIcon,
|
|
2119
|
+
text: "Replace",
|
|
2120
|
+
mode: "ghost",
|
|
2121
|
+
tone: "primary",
|
|
2122
|
+
fontSize: 1,
|
|
2123
|
+
padding: 2,
|
|
2124
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2125
|
+
disabled: isSubmitting
|
|
2126
|
+
}
|
|
2127
|
+
)
|
|
2128
|
+
] })
|
|
2129
|
+
] }),
|
|
2130
|
+
/* @__PURE__ */ jsx(
|
|
2131
|
+
"input",
|
|
2132
|
+
{
|
|
2133
|
+
ref: fileInputRef,
|
|
2134
|
+
type: "file",
|
|
2135
|
+
accept: ".vtt,text/vtt",
|
|
2136
|
+
style: { display: "none" },
|
|
2137
|
+
onChange: (e) => {
|
|
2138
|
+
e.target.files && e.target.files.length > 0 && !isSubmitting && (setSelectedFile(e.target.files[0]), setVttUrl(""));
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
),
|
|
2142
|
+
selectedFile && /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, style: { marginTop: 8 }, children: [
|
|
2143
|
+
"Selected: ",
|
|
2144
|
+
selectedFile.name
|
|
2145
|
+
] })
|
|
2146
|
+
] }),
|
|
2147
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2148
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "vtt-url", children: "VTT File URL" }),
|
|
2149
|
+
/* @__PURE__ */ jsx(
|
|
2150
|
+
TextInput,
|
|
2151
|
+
{
|
|
2152
|
+
id: "vtt-url",
|
|
2153
|
+
placeholder: "https://example.com/subtitles.vtt",
|
|
2154
|
+
value: vttUrl,
|
|
2155
|
+
onChange: (e) => {
|
|
2156
|
+
setVttUrl(e.currentTarget.value), setSelectedFile(null);
|
|
2157
|
+
},
|
|
2158
|
+
disabled: isSubmitting
|
|
2159
|
+
}
|
|
2160
|
+
),
|
|
2161
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Add a URL to replace the existing VTT file with a new one" })
|
|
2162
|
+
] })
|
|
2163
|
+
] }),
|
|
2164
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2165
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-name", children: "Audio name" }),
|
|
2166
|
+
/* @__PURE__ */ jsx(
|
|
2167
|
+
Autocomplete,
|
|
2168
|
+
{
|
|
2169
|
+
id: "caption-name",
|
|
2170
|
+
value: selectedLanguage?.value || "",
|
|
2171
|
+
onChange: (newValue) => {
|
|
2172
|
+
const selected = LANGUAGE_OPTIONS.find((opt) => opt.value === newValue);
|
|
2173
|
+
selected && (setSelectedLanguage(selected), setLanguageCode(selected.value), setName(selected.label));
|
|
2174
|
+
},
|
|
2175
|
+
options: LANGUAGE_OPTIONS,
|
|
2176
|
+
icon: TranslateIcon,
|
|
2177
|
+
placeholder: "Select language",
|
|
2178
|
+
filterOption: (query, option) => option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 || option.value.toLowerCase().indexOf(query.toLowerCase()) > -1,
|
|
2179
|
+
openButton: !0,
|
|
2180
|
+
renderValue: (value) => LANGUAGE_OPTIONS.find((l) => l.value === value)?.label || value,
|
|
2181
|
+
renderOption: (option) => /* @__PURE__ */ jsx(Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: /* @__PURE__ */ jsxs(Text, { size: 2, textOverflow: "ellipsis", children: [
|
|
2182
|
+
option.label,
|
|
2183
|
+
" (",
|
|
2184
|
+
option.value,
|
|
2185
|
+
")"
|
|
2186
|
+
] }) }),
|
|
2187
|
+
disabled: isSubmitting
|
|
2188
|
+
}
|
|
2189
|
+
)
|
|
2190
|
+
] }),
|
|
2191
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2192
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-language", children: "Language Code" }),
|
|
2193
|
+
/* @__PURE__ */ jsx(
|
|
2194
|
+
TextInput,
|
|
2195
|
+
{
|
|
2196
|
+
id: "caption-language",
|
|
2197
|
+
placeholder: "en-US",
|
|
2198
|
+
value: languageCode,
|
|
2199
|
+
onChange: (e) => {
|
|
2200
|
+
setLanguageCode(e.currentTarget.value), selectedLanguage && selectedLanguage.value !== e.currentTarget.value && (setSelectedLanguage(null), (!name2 || name2 === selectedLanguage.label) && setName(""));
|
|
2201
|
+
},
|
|
2202
|
+
disabled: isSubmitting
|
|
2203
|
+
}
|
|
2204
|
+
)
|
|
2205
|
+
] }),
|
|
2206
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, justify: "flex-end", marginTop: 2, children: [
|
|
2207
|
+
/* @__PURE__ */ jsx(Button, { text: "Cancel", mode: "ghost", onClick: onClose, disabled: isSubmitting }),
|
|
2208
|
+
/* @__PURE__ */ jsx(
|
|
2209
|
+
Button,
|
|
2210
|
+
{
|
|
2211
|
+
text: "Update Caption Track",
|
|
2212
|
+
tone: "primary",
|
|
2213
|
+
icon: isSubmitting ? /* @__PURE__ */ jsx(
|
|
2214
|
+
Spinner,
|
|
2215
|
+
{
|
|
2216
|
+
style: {
|
|
2217
|
+
verticalAlign: "middle",
|
|
2218
|
+
display: "inline-block",
|
|
2219
|
+
marginBottom: "-3px",
|
|
2220
|
+
width: "1em",
|
|
2221
|
+
height: "1em",
|
|
2222
|
+
marginRight: "-6px"
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
) : UploadIcon,
|
|
2226
|
+
onClick: handleSubmit,
|
|
2227
|
+
disabled: isSubmitting
|
|
2228
|
+
}
|
|
2229
|
+
)
|
|
2230
|
+
] })
|
|
2231
|
+
] })
|
|
2232
|
+
}
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
function TrackCard({
|
|
2236
|
+
track,
|
|
2237
|
+
iconOnly,
|
|
2238
|
+
downloadingTrackId,
|
|
2239
|
+
deletingTrackId,
|
|
2240
|
+
trackToEdit,
|
|
2241
|
+
getTrackSourceLabel,
|
|
2242
|
+
handleDownload,
|
|
2243
|
+
setTrackToEdit,
|
|
2244
|
+
setTrackToDelete
|
|
2245
|
+
}) {
|
|
2246
|
+
const isDisabled = (action) => action === "download" ? downloadingTrackId !== null || deletingTrackId === track.id || trackToEdit?.id === track.id : action === "edit" ? downloadingTrackId === track.id || deletingTrackId === track.id || trackToEdit?.id === track.id : downloadingTrackId === track.id || deletingTrackId !== null || trackToEdit?.id === track.id, renderActionButtons = () => track.status === "preparing" ? /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
2247
|
+
/* @__PURE__ */ jsx(
|
|
2248
|
+
Spinner,
|
|
2249
|
+
{
|
|
2250
|
+
muted: !0,
|
|
2251
|
+
style: {
|
|
2252
|
+
width: "0.75em",
|
|
2253
|
+
height: "0.75em",
|
|
2254
|
+
verticalAlign: "middle",
|
|
2255
|
+
display: "inline-block",
|
|
2256
|
+
marginBottom: "-2px"
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
),
|
|
2260
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Processing..." })
|
|
2261
|
+
] }) : /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
2262
|
+
track.status !== "errored" && /* @__PURE__ */ jsx(
|
|
2263
|
+
Button,
|
|
2264
|
+
{
|
|
2265
|
+
icon: downloadingTrackId === track.id ? /* @__PURE__ */ jsx(
|
|
2266
|
+
Spinner,
|
|
2267
|
+
{
|
|
2268
|
+
style: {
|
|
2269
|
+
verticalAlign: "middle",
|
|
2270
|
+
display: "inline-block",
|
|
2271
|
+
marginTop: "-2px",
|
|
2272
|
+
width: "0.5em",
|
|
2273
|
+
height: "0.5em"
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
) : /* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
2277
|
+
text: iconOnly ? void 0 : "Download",
|
|
2278
|
+
mode: "ghost",
|
|
2279
|
+
tone: "primary",
|
|
2280
|
+
fontSize: 1,
|
|
2281
|
+
padding: 2,
|
|
2282
|
+
onClick: () => handleDownload(track),
|
|
2283
|
+
disabled: isDisabled("download"),
|
|
2284
|
+
title: "Download"
|
|
2285
|
+
}
|
|
2286
|
+
),
|
|
2287
|
+
/* @__PURE__ */ jsx(
|
|
2288
|
+
Button,
|
|
2289
|
+
{
|
|
2290
|
+
icon: /* @__PURE__ */ jsx(EditIcon, {}),
|
|
2291
|
+
text: iconOnly ? void 0 : "Edit",
|
|
2292
|
+
mode: "ghost",
|
|
2293
|
+
tone: "primary",
|
|
2294
|
+
fontSize: 1,
|
|
2295
|
+
padding: 2,
|
|
2296
|
+
disabled: isDisabled("edit"),
|
|
2297
|
+
onClick: () => setTrackToEdit(track),
|
|
2298
|
+
title: "Edit"
|
|
2299
|
+
}
|
|
2300
|
+
),
|
|
2301
|
+
/* @__PURE__ */ jsx(
|
|
2302
|
+
Button,
|
|
2303
|
+
{
|
|
2304
|
+
icon: deletingTrackId === track.id ? /* @__PURE__ */ jsx(
|
|
2305
|
+
Spinner,
|
|
2306
|
+
{
|
|
2307
|
+
style: {
|
|
2308
|
+
verticalAlign: "middle",
|
|
2309
|
+
display: "inline-block",
|
|
2310
|
+
marginTop: "-2px",
|
|
2311
|
+
width: "0.5em",
|
|
2312
|
+
height: "0.5em"
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
) : /* @__PURE__ */ jsx(TrashIcon, {}),
|
|
2316
|
+
text: iconOnly ? void 0 : "Delete",
|
|
2317
|
+
mode: "ghost",
|
|
2318
|
+
tone: "critical",
|
|
2319
|
+
fontSize: 1,
|
|
2320
|
+
padding: 2,
|
|
2321
|
+
disabled: isDisabled("delete"),
|
|
2322
|
+
onClick: () => setTrackToDelete(track),
|
|
2323
|
+
title: "Delete"
|
|
2324
|
+
}
|
|
2325
|
+
)
|
|
1348
2326
|
] });
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 24 24", ...props, children: /* @__PURE__ */ jsx(
|
|
1352
|
-
"path",
|
|
2327
|
+
return /* @__PURE__ */ jsx(
|
|
2328
|
+
Card,
|
|
1353
2329
|
{
|
|
1354
|
-
|
|
1355
|
-
|
|
2330
|
+
padding: 3,
|
|
2331
|
+
radius: 2,
|
|
2332
|
+
tone: track.status === "errored" ? "caution" : "transparent",
|
|
2333
|
+
border: !0,
|
|
2334
|
+
children: /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 3, children: [
|
|
2335
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, flex: 1, children: [
|
|
2336
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
2337
|
+
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: track.name || "Untitled" }),
|
|
2338
|
+
/* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
2339
|
+
"(",
|
|
2340
|
+
getTrackSourceLabel(track),
|
|
2341
|
+
")"
|
|
2342
|
+
] }),
|
|
2343
|
+
track.status === "errored" && /* @__PURE__ */ jsx(
|
|
2344
|
+
ErrorOutlineIcon,
|
|
2345
|
+
{
|
|
2346
|
+
style: { color: "var(--card-critical-color)" },
|
|
2347
|
+
"aria-label": "Error",
|
|
2348
|
+
fontSize: 20
|
|
2349
|
+
}
|
|
2350
|
+
)
|
|
2351
|
+
] }),
|
|
2352
|
+
track.language_code && /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
2353
|
+
"Language: ",
|
|
2354
|
+
track.language_code
|
|
2355
|
+
] }),
|
|
2356
|
+
track.status === "errored" && track.error && /* @__PURE__ */ jsx(Text, { size: 1, style: { color: "var(--card-critical-color)" }, children: track.error.messages?.[0] || track.error.type || "Failed to process track" })
|
|
2357
|
+
] }),
|
|
2358
|
+
renderActionButtons()
|
|
2359
|
+
] })
|
|
1356
2360
|
}
|
|
1357
|
-
)
|
|
2361
|
+
);
|
|
1358
2362
|
}
|
|
1359
|
-
function
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
2363
|
+
function TextTracksManager({
|
|
2364
|
+
asset,
|
|
2365
|
+
iconOnly = !1,
|
|
2366
|
+
tracks: propTracks,
|
|
2367
|
+
collapseTracks = !1
|
|
2368
|
+
}) {
|
|
2369
|
+
const client = useClient(), toast = useToast(), dialogId = `DeleteCaptionDialog${useId()}`, [downloadingTrackId, setDownloadingTrackId] = useState(null), [deletingTrackId, setDeletingTrackId] = useState(null), [addedTracks, setAddedTracks] = useState([]), [updatedTracks, setUpdatedTracks] = useState(/* @__PURE__ */ new Map()), [trackActivityOrder, setTrackActivityOrder] = useState(/* @__PURE__ */ new Map()), [autogeneratedTrackIds, setAutogeneratedTrackIds] = useState(/* @__PURE__ */ new Set()), [trackToDelete, setTrackToDelete] = useState(null), [trackToEdit, setTrackToEdit] = useState(null), [showAddDialog, setShowAddDialog] = useState(!1), [isExpanded, setIsExpanded] = useState(!1), MAX_VISIBLE_TRACKS = 4;
|
|
2370
|
+
useEffect(() => {
|
|
2371
|
+
if (!asset.assetId || !asset._id) return;
|
|
2372
|
+
const assetId = asset.assetId, documentId = asset._id;
|
|
2373
|
+
(async () => {
|
|
2374
|
+
try {
|
|
2375
|
+
const response = await getAsset(client, assetId);
|
|
2376
|
+
await client.patch(documentId).set({ data: response.data, status: response.data.status }).commit();
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
console.error("Failed to refresh asset data:", error);
|
|
2379
|
+
}
|
|
2380
|
+
})();
|
|
2381
|
+
}, [asset.assetId, asset._id, client]);
|
|
2382
|
+
const activeTracks = (propTracks || asset.data?.tracks?.filter((track) => track.type === "text") || []).filter(
|
|
2383
|
+
(track) => track.id && (track.status === "ready" || track.status === "preparing" || track.status === "errored")
|
|
2384
|
+
), allTracks = useMemo(() => {
|
|
2385
|
+
const tracksWithUpdates = activeTracks.map((track) => updatedTracks.get(track.id) || track), isMockTrackReplaced = (mockTrack, realTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : realTracksList.some((realTrack) => {
|
|
2386
|
+
const nameMatches = realTrack.name === mockTrack.name, languageMatches = realTrack.language_code === mockTrack.language_code;
|
|
2387
|
+
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";
|
|
2388
|
+
}), isTrackAlreadyInRealTracks = (addedTrack, realTracksList) => addedTrack.id ? addedTrack.id.startsWith("generating-") ? isMockTrackReplaced(addedTrack, realTracksList) : realTracksList.some((realTrack) => realTrack.id === addedTrack.id) : !1, tracksToKeep = addedTracks.filter((addedTrack) => addedTrack.id && addedTrack.id.startsWith("generating-") ? !isMockTrackReplaced(addedTrack, tracksWithUpdates) : !isTrackAlreadyInRealTracks(addedTrack, tracksWithUpdates));
|
|
2389
|
+
return [...tracksWithUpdates, ...tracksToKeep];
|
|
2390
|
+
}, [activeTracks, addedTracks, updatedTracks]);
|
|
2391
|
+
useEffect(() => {
|
|
2392
|
+
const newAutogeneratedIds = /* @__PURE__ */ new Set();
|
|
2393
|
+
activeTracks.forEach((track) => {
|
|
2394
|
+
track.id && (track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod") && newAutogeneratedIds.add(track.id);
|
|
2395
|
+
}), addedTracks.forEach((mockTrack) => {
|
|
2396
|
+
if (mockTrack.id && mockTrack.id.startsWith("generating-")) {
|
|
2397
|
+
const realTrack = activeTracks.find((rt) => {
|
|
2398
|
+
const nameMatches = rt.name === mockTrack.name, languageMatches = rt.language_code === mockTrack.language_code;
|
|
2399
|
+
return nameMatches && languageMatches;
|
|
2400
|
+
});
|
|
2401
|
+
realTrack?.id && newAutogeneratedIds.add(realTrack.id);
|
|
2402
|
+
}
|
|
2403
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2404
|
+
let hasNew = !1;
|
|
2405
|
+
const updated = new Set(prev);
|
|
2406
|
+
return newAutogeneratedIds.forEach((id) => {
|
|
2407
|
+
prev.has(id) || (updated.add(id), hasNew = !0);
|
|
2408
|
+
}), hasNew ? updated : prev;
|
|
2409
|
+
});
|
|
2410
|
+
}, [activeTracks, addedTracks]), useEffect(() => {
|
|
2411
|
+
if (allTracks.filter((track) => track.status === "preparing").length === 0 || !asset.assetId || !asset._id)
|
|
2412
|
+
return;
|
|
2413
|
+
const assetId = asset.assetId, documentId = asset._id, interval = setInterval(async () => {
|
|
2414
|
+
try {
|
|
2415
|
+
const response = await getAsset(client, assetId);
|
|
2416
|
+
await client.patch(documentId).set({ data: response.data, status: response.data.status }).commit();
|
|
2417
|
+
const fetchedTracks = response.data.tracks?.filter((track) => track.type === "text") || [], isMockTrackReplaced = (mockTrack, fetchedTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : fetchedTracksList.some((realTrack) => {
|
|
2418
|
+
const nameMatches = realTrack.name === mockTrack.name, languageMatches = realTrack.language_code === mockTrack.language_code;
|
|
2419
|
+
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";
|
|
2420
|
+
}), newAutogeneratedIds = /* @__PURE__ */ new Set();
|
|
2421
|
+
fetchedTracks.forEach((track) => {
|
|
2422
|
+
track.id && (track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod") && newAutogeneratedIds.add(track.id);
|
|
2423
|
+
});
|
|
2424
|
+
const findMatchingRealTrack = (mockTrack, tracksList) => tracksList.find((rt) => {
|
|
2425
|
+
const nameMatches = rt.name === mockTrack.name, languageMatches = rt.language_code === mockTrack.language_code;
|
|
2426
|
+
return nameMatches && languageMatches;
|
|
2427
|
+
});
|
|
2428
|
+
setAddedTracks((prev) => prev.filter((mockTrack) => {
|
|
2429
|
+
if (mockTrack.id && mockTrack.id.startsWith("generating-")) {
|
|
2430
|
+
const replaced = isMockTrackReplaced(mockTrack, fetchedTracks);
|
|
2431
|
+
if (replaced) {
|
|
2432
|
+
const realTrack = findMatchingRealTrack(mockTrack, fetchedTracks);
|
|
2433
|
+
realTrack?.id && (newAutogeneratedIds.add(realTrack.id), setTrackActivityOrder((prevOrder) => {
|
|
2434
|
+
const mockOrder = prevOrder.get(mockTrack.id);
|
|
2435
|
+
if (mockOrder) {
|
|
2436
|
+
const newMap = new Map(prevOrder);
|
|
2437
|
+
return newMap.set(realTrack.id, mockOrder), newMap;
|
|
2438
|
+
}
|
|
2439
|
+
return prevOrder;
|
|
2440
|
+
}));
|
|
2441
|
+
}
|
|
2442
|
+
return !replaced;
|
|
1375
2443
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
2444
|
+
return !0;
|
|
2445
|
+
})), newAutogeneratedIds.size > 0 && setAutogeneratedTrackIds((prevIds) => {
|
|
2446
|
+
const updated = new Set(prevIds);
|
|
2447
|
+
return newAutogeneratedIds.forEach((id) => updated.add(id)), updated;
|
|
2448
|
+
});
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
console.error("Failed to refresh asset data:", error);
|
|
2451
|
+
}
|
|
2452
|
+
}, 3e3);
|
|
2453
|
+
return () => clearInterval(interval);
|
|
2454
|
+
}, [allTracks, asset.assetId, asset._id, client]);
|
|
2455
|
+
const visibleTracks = allTracks.filter(
|
|
2456
|
+
(track) => track.status === "ready" || track.status === "preparing" || track.status === "errored"
|
|
2457
|
+
).sort((a2, b) => {
|
|
2458
|
+
const orderA = trackActivityOrder.get(a2.id) || 0, orderB = trackActivityOrder.get(b.id) || 0;
|
|
2459
|
+
if (orderA > 0 && orderB > 0)
|
|
2460
|
+
return orderB - orderA;
|
|
2461
|
+
if (orderA > 0) return -1;
|
|
2462
|
+
if (orderB > 0) return 1;
|
|
2463
|
+
const aIsPreparing = a2.status === "preparing", bIsPreparing = b.status === "preparing";
|
|
2464
|
+
if (aIsPreparing && !bIsPreparing) return -1;
|
|
2465
|
+
if (!aIsPreparing && bIsPreparing) return 1;
|
|
2466
|
+
const aIsAutogenerated = a2.id && a2.id.startsWith("generating-") || a2.id && autogeneratedTrackIds.has(a2.id), bIsAutogenerated = b.id && b.id.startsWith("generating-") || b.id && autogeneratedTrackIds.has(b.id);
|
|
2467
|
+
return aIsAutogenerated && !bIsAutogenerated ? -1 : !aIsAutogenerated && bIsAutogenerated ? 1 : 0;
|
|
2468
|
+
}), handleDownload = async (track) => {
|
|
2469
|
+
if (track.id) {
|
|
2470
|
+
setDownloadingTrackId(track.id);
|
|
2471
|
+
try {
|
|
2472
|
+
await downloadVttFile(client, asset, track);
|
|
2473
|
+
} catch (error) {
|
|
2474
|
+
toast.push({
|
|
2475
|
+
title: "Failed to download VTT file",
|
|
2476
|
+
status: "error",
|
|
2477
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2478
|
+
});
|
|
2479
|
+
} finally {
|
|
2480
|
+
setDownloadingTrackId(null);
|
|
2481
|
+
}
|
|
1379
2482
|
}
|
|
1380
|
-
)
|
|
2483
|
+
}, confirmDelete = async () => {
|
|
2484
|
+
if (!trackToDelete || !trackToDelete.id) return;
|
|
2485
|
+
const track = trackToDelete;
|
|
2486
|
+
setTrackToDelete(null), setDeletingTrackId(track.id);
|
|
2487
|
+
try {
|
|
2488
|
+
if (!asset.assetId)
|
|
2489
|
+
throw new Error("Asset ID is required");
|
|
2490
|
+
if (await deleteTextTrack(client, asset.assetId, track.id), asset._id)
|
|
2491
|
+
try {
|
|
2492
|
+
const response = await getAsset(client, asset.assetId);
|
|
2493
|
+
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2494
|
+
} catch (refreshError) {
|
|
2495
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2496
|
+
}
|
|
2497
|
+
toast.push({
|
|
2498
|
+
title: "Successfully deleted caption track",
|
|
2499
|
+
status: "success"
|
|
2500
|
+
}), setAddedTracks((prev) => prev.filter((t) => t.id !== track.id)), setUpdatedTracks((prev) => {
|
|
2501
|
+
const newMap = new Map(prev);
|
|
2502
|
+
return newMap.delete(track.id), newMap;
|
|
2503
|
+
}), setTrackActivityOrder((prev) => {
|
|
2504
|
+
const newMap = new Map(prev);
|
|
2505
|
+
return newMap.delete(track.id), newMap;
|
|
2506
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2507
|
+
const updated = new Set(prev);
|
|
2508
|
+
return updated.delete(track.id), updated;
|
|
2509
|
+
});
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
toast.push({
|
|
2512
|
+
title: "Failed to delete caption track",
|
|
2513
|
+
status: "error",
|
|
2514
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2515
|
+
});
|
|
2516
|
+
} finally {
|
|
2517
|
+
setDeletingTrackId(null);
|
|
2518
|
+
}
|
|
2519
|
+
}, handleAddTrack = (track) => {
|
|
2520
|
+
setAddedTracks((prev) => [...prev, track]), setTrackActivityOrder((prev) => {
|
|
2521
|
+
const newMap = new Map(prev);
|
|
2522
|
+
return newMap.set(track.id, prev.size + 1), newMap;
|
|
2523
|
+
}), setShowAddDialog(!1);
|
|
2524
|
+
}, handleUpdateTrack = async (updatedTrack, oldTrackId) => {
|
|
2525
|
+
if (oldTrackId && (setAddedTracks((prev) => prev.filter((t) => t.id !== oldTrackId)), setUpdatedTracks((prev) => {
|
|
2526
|
+
const newMap = new Map(prev);
|
|
2527
|
+
return newMap.delete(oldTrackId), newMap;
|
|
2528
|
+
}), setTrackActivityOrder((prev) => {
|
|
2529
|
+
const newMap = new Map(prev);
|
|
2530
|
+
return newMap.delete(oldTrackId), newMap;
|
|
2531
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2532
|
+
const updated = new Set(prev);
|
|
2533
|
+
return updated.delete(oldTrackId), updated;
|
|
2534
|
+
})), addedTracks.some((t) => t.id === updatedTrack.id) ? setAddedTracks((prev) => prev.map((t) => t.id === updatedTrack.id ? updatedTrack : t)) : setUpdatedTracks((prev) => {
|
|
2535
|
+
const newMap = new Map(prev);
|
|
2536
|
+
return newMap.set(updatedTrack.id, updatedTrack), newMap;
|
|
2537
|
+
}), setTrackActivityOrder((prev) => {
|
|
2538
|
+
const newMap = new Map(prev);
|
|
2539
|
+
return newMap.set(updatedTrack.id, prev.size + 1), newMap;
|
|
2540
|
+
}), setTrackToEdit(null), asset._id && asset.assetId)
|
|
2541
|
+
try {
|
|
2542
|
+
const response = await getAsset(client, asset.assetId);
|
|
2543
|
+
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2544
|
+
} catch (refreshError) {
|
|
2545
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2546
|
+
}
|
|
2547
|
+
}, 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";
|
|
2548
|
+
if (visibleTracks.length === 0 && !showAddDialog)
|
|
2549
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2550
|
+
/* @__PURE__ */ jsx(Flex, { justify: "flex-end", children: /* @__PURE__ */ jsx(
|
|
2551
|
+
Button,
|
|
2552
|
+
{
|
|
2553
|
+
icon: AddIcon,
|
|
2554
|
+
text: "Add Caption",
|
|
2555
|
+
tone: "primary",
|
|
2556
|
+
onClick: () => setShowAddDialog(!0)
|
|
2557
|
+
}
|
|
2558
|
+
) }),
|
|
2559
|
+
/* @__PURE__ */ jsx(Card, { padding: 4, radius: 2, tone: "transparent", border: !0, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "No captions available. Add captions when uploading a video or add them manually." }) }),
|
|
2560
|
+
showAddDialog && /* @__PURE__ */ jsx(
|
|
2561
|
+
AddCaptionDialog,
|
|
2562
|
+
{
|
|
2563
|
+
asset,
|
|
2564
|
+
onAdd: handleAddTrack,
|
|
2565
|
+
onClose: () => setShowAddDialog(!1)
|
|
2566
|
+
}
|
|
2567
|
+
)
|
|
2568
|
+
] });
|
|
2569
|
+
const displayedTracks = collapseTracks && !isExpanded ? visibleTracks.slice(0, MAX_VISIBLE_TRACKS) : visibleTracks, hasMoreTracks = collapseTracks && visibleTracks.length > MAX_VISIBLE_TRACKS;
|
|
2570
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2571
|
+
/* @__PURE__ */ jsx(Flex, { justify: "flex-end", children: /* @__PURE__ */ jsx(
|
|
2572
|
+
Button,
|
|
2573
|
+
{
|
|
2574
|
+
icon: AddIcon,
|
|
2575
|
+
text: "Add Caption",
|
|
2576
|
+
tone: "primary",
|
|
2577
|
+
onClick: () => setShowAddDialog(!0)
|
|
2578
|
+
}
|
|
2579
|
+
) }),
|
|
2580
|
+
displayedTracks.map((track) => /* @__PURE__ */ jsx(
|
|
2581
|
+
TrackCard,
|
|
2582
|
+
{
|
|
2583
|
+
track,
|
|
2584
|
+
iconOnly,
|
|
2585
|
+
downloadingTrackId,
|
|
2586
|
+
deletingTrackId,
|
|
2587
|
+
trackToEdit,
|
|
2588
|
+
getTrackSourceLabel,
|
|
2589
|
+
handleDownload,
|
|
2590
|
+
setTrackToEdit,
|
|
2591
|
+
setTrackToDelete
|
|
2592
|
+
},
|
|
2593
|
+
track.id
|
|
2594
|
+
)),
|
|
2595
|
+
hasMoreTracks && /* @__PURE__ */ jsx(Flex, { justify: "center", children: /* @__PURE__ */ jsx(
|
|
2596
|
+
Button,
|
|
2597
|
+
{
|
|
2598
|
+
icon: isExpanded ? ChevronUpIcon : ChevronDownIcon,
|
|
2599
|
+
text: isExpanded ? "Show less" : `Show ${visibleTracks.length - MAX_VISIBLE_TRACKS} more`,
|
|
2600
|
+
mode: "ghost",
|
|
2601
|
+
tone: "primary",
|
|
2602
|
+
onClick: () => setIsExpanded(!isExpanded)
|
|
2603
|
+
}
|
|
2604
|
+
) }),
|
|
2605
|
+
trackToDelete && /* @__PURE__ */ jsx(
|
|
2606
|
+
Dialog,
|
|
2607
|
+
{
|
|
2608
|
+
animate: !0,
|
|
2609
|
+
id: dialogId,
|
|
2610
|
+
header: "Delete track",
|
|
2611
|
+
onClose: () => setTrackToDelete(null),
|
|
2612
|
+
onClickOutside: () => setTrackToDelete(null),
|
|
2613
|
+
width: 1,
|
|
2614
|
+
children: /* @__PURE__ */ jsx(
|
|
2615
|
+
Card,
|
|
2616
|
+
{
|
|
2617
|
+
padding: 3,
|
|
2618
|
+
style: {
|
|
2619
|
+
minHeight: "150px",
|
|
2620
|
+
display: "flex",
|
|
2621
|
+
alignItems: "center",
|
|
2622
|
+
justifyContent: "center"
|
|
2623
|
+
},
|
|
2624
|
+
children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2625
|
+
/* @__PURE__ */ jsxs(Heading, { size: 2, children: [
|
|
2626
|
+
'Are you sure you want to delete "',
|
|
2627
|
+
trackToDelete.name || trackToDelete.language_code || "Untitled",
|
|
2628
|
+
'"?'
|
|
2629
|
+
] }),
|
|
2630
|
+
/* @__PURE__ */ jsx(Text, { size: 2, children: "This action is irreversible" }),
|
|
2631
|
+
/* @__PURE__ */ jsx(Stack, { space: 4, marginY: 4, children: /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
|
|
2632
|
+
Button,
|
|
2633
|
+
{
|
|
2634
|
+
icon: deletingTrackId === trackToDelete.id ? /* @__PURE__ */ jsx(
|
|
2635
|
+
Spinner,
|
|
2636
|
+
{
|
|
2637
|
+
style: {
|
|
2638
|
+
verticalAlign: "middle",
|
|
2639
|
+
display: "inline-block",
|
|
2640
|
+
marginTop: "-2px",
|
|
2641
|
+
width: "0.5em",
|
|
2642
|
+
height: "0.5em"
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
) : /* @__PURE__ */ jsx(TrashIcon, {}),
|
|
2646
|
+
fontSize: 2,
|
|
2647
|
+
padding: 3,
|
|
2648
|
+
text: "Delete track",
|
|
2649
|
+
tone: "critical",
|
|
2650
|
+
onClick: confirmDelete,
|
|
2651
|
+
disabled: deletingTrackId !== null
|
|
2652
|
+
}
|
|
2653
|
+
) }) })
|
|
2654
|
+
] })
|
|
2655
|
+
}
|
|
2656
|
+
)
|
|
2657
|
+
}
|
|
2658
|
+
),
|
|
2659
|
+
showAddDialog && /* @__PURE__ */ jsx(
|
|
2660
|
+
AddCaptionDialog,
|
|
2661
|
+
{
|
|
2662
|
+
asset,
|
|
2663
|
+
onAdd: handleAddTrack,
|
|
2664
|
+
onClose: () => setShowAddDialog(!1)
|
|
2665
|
+
}
|
|
2666
|
+
),
|
|
2667
|
+
trackToEdit && /* @__PURE__ */ jsx(
|
|
2668
|
+
EditCaptionDialog,
|
|
2669
|
+
{
|
|
2670
|
+
asset,
|
|
2671
|
+
track: trackToEdit,
|
|
2672
|
+
onUpdate: handleUpdateTrack,
|
|
2673
|
+
onClose: () => setTrackToEdit(null)
|
|
2674
|
+
}
|
|
2675
|
+
)
|
|
2676
|
+
] });
|
|
1381
2677
|
}
|
|
1382
2678
|
const DialogStateContext = createContext({
|
|
1383
2679
|
dialogState: !1,
|
|
@@ -1395,6 +2691,10 @@ function getVideoSrc({ asset, client }) {
|
|
|
1395
2691
|
}
|
|
1396
2692
|
return `https://stream.mux.com/${playbackId}.m3u8?${searchParams}`;
|
|
1397
2693
|
}
|
|
2694
|
+
function CaptionsDialog({ asset }) {
|
|
2695
|
+
const { setDialogState } = useDialogStateContext(), dialogId = `CaptionsDialog${useId()}`;
|
|
2696
|
+
return /* @__PURE__ */ jsx(Dialog, { id: dialogId, header: "Edit Captions", onClose: () => setDialogState(!1), width: 1, children: /* @__PURE__ */ jsx(Stack, { padding: 4, children: /* @__PURE__ */ jsx(TextTracksManager, { asset }) }) });
|
|
2697
|
+
}
|
|
1398
2698
|
function getDevicePixelRatio(options) {
|
|
1399
2699
|
const {
|
|
1400
2700
|
defaultDpr = 1,
|
|
@@ -1560,7 +2860,7 @@ function VideoPlayer({
|
|
|
1560
2860
|
crossOrigin: "anonymous",
|
|
1561
2861
|
metadata: {
|
|
1562
2862
|
player_name: "Sanity Admin Dashboard",
|
|
1563
|
-
player_version: "2.
|
|
2863
|
+
player_version: "2.14.0",
|
|
1564
2864
|
page_type: "Preview Player"
|
|
1565
2865
|
},
|
|
1566
2866
|
audio: isAudio,
|
|
@@ -1595,7 +2895,8 @@ function VideoPlayer({
|
|
|
1595
2895
|
]
|
|
1596
2896
|
}
|
|
1597
2897
|
),
|
|
1598
|
-
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime })
|
|
2898
|
+
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime }),
|
|
2899
|
+
dialogState === "edit-captions" && /* @__PURE__ */ jsx(CaptionsDialog, { asset })
|
|
1599
2900
|
] });
|
|
1600
2901
|
}
|
|
1601
2902
|
function assetIsAudio(asset) {
|
|
@@ -1867,7 +3168,8 @@ function getVideoMetadata(doc) {
|
|
|
1867
3168
|
duration: doc.data?.duration ? formatSeconds(doc.data?.duration) : void 0,
|
|
1868
3169
|
aspect_ratio: doc.data?.aspect_ratio,
|
|
1869
3170
|
max_stored_resolution: doc.data?.max_stored_resolution,
|
|
1870
|
-
max_stored_frame_rate: doc.data?.max_stored_frame_rate
|
|
3171
|
+
max_stored_frame_rate: doc.data?.max_stored_frame_rate,
|
|
3172
|
+
text_tracks: doc.data?.tracks?.filter((track) => track.type === "text") || []
|
|
1871
3173
|
};
|
|
1872
3174
|
}
|
|
1873
3175
|
function useVideoDetails(props) {
|
|
@@ -2059,7 +3361,20 @@ const AssetInput = (props) => /* @__PURE__ */ jsx(FormField$1, { title: props.la
|
|
|
2059
3361
|
minHeight: containerHeight
|
|
2060
3362
|
} : void 0,
|
|
2061
3363
|
children: [
|
|
2062
|
-
/* @__PURE__ */
|
|
3364
|
+
/* @__PURE__ */ jsxs(Stack, { space: 4, flex: 1, sizing: "border", children: [
|
|
3365
|
+
/* @__PURE__ */ jsx(VideoPlayer, { asset: props.asset, autoPlay: props.asset.autoPlay || !1 }),
|
|
3366
|
+
tab === "details" && /* @__PURE__ */ jsx(
|
|
3367
|
+
TextTracksManager,
|
|
3368
|
+
{
|
|
3369
|
+
asset: props.asset,
|
|
3370
|
+
iconOnly: !0,
|
|
3371
|
+
collapseTracks: !0,
|
|
3372
|
+
tracks: displayInfo?.text_tracks || props.asset.data?.tracks?.filter(
|
|
3373
|
+
(track) => track.type === "text"
|
|
3374
|
+
) || []
|
|
3375
|
+
}
|
|
3376
|
+
)
|
|
3377
|
+
] }),
|
|
2063
3378
|
/* @__PURE__ */ jsxs(Stack, { space: 4, flex: 1, sizing: "border", children: [
|
|
2064
3379
|
/* @__PURE__ */ jsxs(TabList, { space: 2, children: [
|
|
2065
3380
|
/* @__PURE__ */ jsx(
|
|
@@ -2389,10 +3704,10 @@ function VideoInBrowser({
|
|
|
2389
3704
|
);
|
|
2390
3705
|
}
|
|
2391
3706
|
function VideosBrowser({ onSelect }) {
|
|
2392
|
-
const { assets, isLoading, searchQuery, setSearchQuery, setSort, sort } = useAssets(), [editedAsset, setEditedAsset] = useState(null), freshEditedAsset = useMemo(
|
|
3707
|
+
const { assets, isLoading, searchQuery, setSearchQuery, setSort, sort } = useAssets(), [page, setPage] = useState(0), pageLimit = 20, pageTotal = Math.floor(assets.length / pageLimit) + 1, [editedAsset, setEditedAsset] = useState(null), freshEditedAsset = useMemo(
|
|
2393
3708
|
() => assets.find((a2) => a2._id === editedAsset?._id) || editedAsset,
|
|
2394
3709
|
[editedAsset, assets]
|
|
2395
|
-
);
|
|
3710
|
+
), pageStart = page * pageLimit, pageEnd = pageStart + pageLimit;
|
|
2396
3711
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2397
3712
|
/* @__PURE__ */ jsxs(Stack, { padding: 4, space: 4, style: { minHeight: "50vh" }, children: [
|
|
2398
3713
|
/* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
|
|
@@ -2406,7 +3721,8 @@ function VideosBrowser({ onSelect }) {
|
|
|
2406
3721
|
placeholder: "Search videos"
|
|
2407
3722
|
}
|
|
2408
3723
|
),
|
|
2409
|
-
/* @__PURE__ */ jsx(SelectSortOptions, { setSort, sort })
|
|
3724
|
+
/* @__PURE__ */ jsx(SelectSortOptions, { setSort, sort }),
|
|
3725
|
+
/* @__PURE__ */ jsx(PageSelector, { page, setPage, total: pageTotal, limit: pageLimit })
|
|
2410
3726
|
] }),
|
|
2411
3727
|
(onSelect ? "input" : "tool") == "tool" && /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
|
|
2412
3728
|
/* @__PURE__ */ jsx(ImportVideosFromMux, {}),
|
|
@@ -2429,7 +3745,7 @@ function VideosBrowser({ onSelect }) {
|
|
|
2429
3745
|
style: {
|
|
2430
3746
|
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))"
|
|
2431
3747
|
},
|
|
2432
|
-
children: assets.map((asset) => /* @__PURE__ */ jsx(
|
|
3748
|
+
children: assets.slice(pageStart, pageEnd).map((asset) => /* @__PURE__ */ jsx(
|
|
2433
3749
|
VideoInBrowser,
|
|
2434
3750
|
{
|
|
2435
3751
|
asset,
|
|
@@ -3137,14 +4453,24 @@ function PlayerActionsMenu(props) {
|
|
|
3137
4453
|
onClick: () => setDialogState("select-video")
|
|
3138
4454
|
}
|
|
3139
4455
|
),
|
|
3140
|
-
isVideoAsset(asset) && /* @__PURE__ */
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
4456
|
+
isVideoAsset(asset) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4457
|
+
/* @__PURE__ */ jsx(
|
|
4458
|
+
MenuItem,
|
|
4459
|
+
{
|
|
4460
|
+
icon: ImageIcon,
|
|
4461
|
+
text: "Thumbnail",
|
|
4462
|
+
onClick: () => setDialogState("edit-thumbnail")
|
|
4463
|
+
}
|
|
4464
|
+
),
|
|
4465
|
+
/* @__PURE__ */ jsx(
|
|
4466
|
+
MenuItem,
|
|
4467
|
+
{
|
|
4468
|
+
icon: TranslateIcon,
|
|
4469
|
+
text: "Captions",
|
|
4470
|
+
onClick: () => setDialogState("edit-captions")
|
|
4471
|
+
}
|
|
4472
|
+
)
|
|
4473
|
+
] }),
|
|
3148
4474
|
/* @__PURE__ */ jsx(MenuDivider, {}),
|
|
3149
4475
|
hasConfigAccess && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3150
4476
|
/* @__PURE__ */ jsx(
|
|
@@ -3198,36 +4524,6 @@ function formatBytes(bytes, si = !1, dp = 1) {
|
|
|
3198
4524
|
while (Math.round(Math.abs(bytes) * r) / r >= thresh && u2 < units.length - 1);
|
|
3199
4525
|
return bytes.toFixed(dp) + " " + units[u2];
|
|
3200
4526
|
}
|
|
3201
|
-
const SUPPORTED_MUX_LANGUAGES = [
|
|
3202
|
-
{ label: "English", code: "en", state: "Stable" },
|
|
3203
|
-
{ label: "Spanish", code: "es", state: "Stable" },
|
|
3204
|
-
{ label: "Italian", code: "it", state: "Stable" },
|
|
3205
|
-
{ label: "Portuguese", code: "pt", state: "Stable" },
|
|
3206
|
-
{ label: "German", code: "de", state: "Stable" },
|
|
3207
|
-
{ label: "French", code: "fr", state: "Stable" },
|
|
3208
|
-
{ label: "Polish", code: "pl", state: "Beta" },
|
|
3209
|
-
{ label: "Russian", code: "ru", state: "Beta" },
|
|
3210
|
-
{ label: "Dutch", code: "nl", state: "Beta" },
|
|
3211
|
-
{ label: "Catalan", code: "ca", state: "Beta" },
|
|
3212
|
-
{ label: "Turkish", code: "tr", state: "Beta" },
|
|
3213
|
-
{ label: "Swedish", code: "sv", state: "Beta" },
|
|
3214
|
-
{ label: "Ukrainian", code: "uk", state: "Beta" },
|
|
3215
|
-
{ label: "Norwegian", code: "no", state: "Beta" },
|
|
3216
|
-
{ label: "Finnish", code: "fi", state: "Beta" },
|
|
3217
|
-
{ label: "Slovak", code: "sk", state: "Beta" },
|
|
3218
|
-
{ label: "Greek", code: "el", state: "Beta" },
|
|
3219
|
-
{ label: "Czech", code: "cs", state: "Beta" },
|
|
3220
|
-
{ label: "Croatian", code: "hr", state: "Beta" },
|
|
3221
|
-
{ label: "Danish", code: "da", state: "Beta" },
|
|
3222
|
-
{ label: "Romanian", code: "ro", state: "Beta" },
|
|
3223
|
-
{ label: "Bulgarian", code: "bg", state: "Beta" }
|
|
3224
|
-
];
|
|
3225
|
-
function isCustomTextTrack(track) {
|
|
3226
|
-
return track.type !== "autogenerated";
|
|
3227
|
-
}
|
|
3228
|
-
function isAutogeneratedTrack(track) {
|
|
3229
|
-
return track.type === "autogenerated";
|
|
3230
|
-
}
|
|
3231
4527
|
const ALL_LANGUAGE_CODES = LanguagesList.getAllCodes().map((code) => ({
|
|
3232
4528
|
value: code,
|
|
3233
4529
|
label: LanguagesList.getNativeName(code)
|
|
@@ -3849,12 +5145,10 @@ function withFocusRing(component) {
|
|
|
3849
5145
|
`;
|
|
3850
5146
|
});
|
|
3851
5147
|
}
|
|
3852
|
-
const
|
|
5148
|
+
const UploadCardWithFocusRing = withFocusRing(Card), UploadCard = forwardRef(
|
|
3853
5149
|
({ children, tone, onPaste, onDrop, onDragEnter, onDragLeave, onDragOver }, forwardedRef) => {
|
|
3854
|
-
const
|
|
3855
|
-
|
|
3856
|
-
}, []), handleKeyUp = useCallback((event) => {
|
|
3857
|
-
(event.keyCode == ctrlKey || event.keyCode == cmdKey) && (ctrlDown.current = !1);
|
|
5150
|
+
const inputRef = useRef(null), handleKeyDown = useCallback((event) => {
|
|
5151
|
+
event.target.closest("#vtt-url") || (event.ctrlKey || event.metaKey) && event.key === "v" && inputRef.current.focus();
|
|
3858
5152
|
}, []);
|
|
3859
5153
|
return /* @__PURE__ */ jsxs(
|
|
3860
5154
|
UploadCardWithFocusRing,
|
|
@@ -3866,14 +5160,13 @@ const ctrlKey = 17, cmdKey = 91, UploadCardWithFocusRing = withFocusRing(Card),
|
|
|
3866
5160
|
shadow: 0,
|
|
3867
5161
|
tabIndex: 0,
|
|
3868
5162
|
onKeyDown: handleKeyDown,
|
|
3869
|
-
onKeyUp: handleKeyUp,
|
|
3870
5163
|
onPaste,
|
|
3871
5164
|
onDrop,
|
|
3872
5165
|
onDragEnter,
|
|
3873
5166
|
onDragLeave,
|
|
3874
5167
|
onDragOver,
|
|
3875
5168
|
children: [
|
|
3876
|
-
/* @__PURE__ */ jsx(HiddenInput$1, { ref: inputRef
|
|
5169
|
+
/* @__PURE__ */ jsx(HiddenInput$1, { ref: inputRef }),
|
|
3877
5170
|
children
|
|
3878
5171
|
]
|
|
3879
5172
|
}
|
|
@@ -4131,6 +5424,8 @@ function Uploader(props) {
|
|
|
4131
5424
|
input: { type: "file", files }
|
|
4132
5425
|
});
|
|
4133
5426
|
}, handlePaste = (event) => {
|
|
5427
|
+
if (event.target.closest("#vtt-url"))
|
|
5428
|
+
return;
|
|
4134
5429
|
event.preventDefault(), event.stopPropagation();
|
|
4135
5430
|
const url = (event.clipboardData || window.clipboardData)?.getData("text")?.trim();
|
|
4136
5431
|
if (!isValidUrl(url)) {
|