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