sanity-plugin-mux-input 3.0.5 → 4.0.1

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.
Files changed (123) hide show
  1. package/dist/index.js +20 -92
  2. package/dist/index.js.map +1 -1
  3. package/package.json +5 -15
  4. package/dist/index.cjs +0 -5746
  5. package/dist/index.cjs.map +0 -1
  6. package/dist/index.d.cts +0 -288
  7. package/dist/index.d.cts.map +0 -1
  8. package/sanity.json +0 -8
  9. package/src/_exports/index.ts +0 -73
  10. package/src/actions/assets.ts +0 -152
  11. package/src/actions/secrets.ts +0 -110
  12. package/src/actions/upload.ts +0 -308
  13. package/src/clients/upChunkObservable.ts +0 -54
  14. package/src/components/AddCaptionDialog.tsx +0 -440
  15. package/src/components/CaptionsDialog.tsx +0 -23
  16. package/src/components/ConfigureApi.styled.tsx +0 -19
  17. package/src/components/ConfigureApi.tsx +0 -296
  18. package/src/components/DraggableWatermark.tsx +0 -885
  19. package/src/components/EditCaptionDialog.tsx +0 -511
  20. package/src/components/EditThumbnailDialog.tsx +0 -121
  21. package/src/components/ErrorBoundaryCard.tsx +0 -97
  22. package/src/components/FileInputButton.tsx +0 -54
  23. package/src/components/FileInputMenuItem.styled.tsx +0 -36
  24. package/src/components/FileInputMenuItem.tsx +0 -85
  25. package/src/components/FormField.tsx +0 -38
  26. package/src/components/IconInfo.tsx +0 -22
  27. package/src/components/ImportVideosFromMux.tsx +0 -339
  28. package/src/components/Input.styled.tsx +0 -22
  29. package/src/components/Input.tsx +0 -78
  30. package/src/components/InputBrowser.tsx +0 -41
  31. package/src/components/MuxLogo.tsx +0 -42
  32. package/src/components/Onboard.tsx +0 -65
  33. package/src/components/PageSelector.tsx +0 -54
  34. package/src/components/Player.styled.tsx +0 -11
  35. package/src/components/Player.tsx +0 -117
  36. package/src/components/PlayerActionsMenu.tsx +0 -191
  37. package/src/components/ResyncMetadata.tsx +0 -278
  38. package/src/components/SelectAsset.tsx +0 -39
  39. package/src/components/SelectSortOptions.tsx +0 -45
  40. package/src/components/SpinnerBox.tsx +0 -16
  41. package/src/components/StudioTool.tsx +0 -24
  42. package/src/components/TextTracksEditor.tsx +0 -117
  43. package/src/components/TextTracksManager.tsx +0 -738
  44. package/src/components/UploadConfiguration.tsx +0 -696
  45. package/src/components/UploadPlaceholder.tsx +0 -88
  46. package/src/components/UploadProgress.tsx +0 -80
  47. package/src/components/Uploader.styled.tsx +0 -65
  48. package/src/components/Uploader.tsx +0 -499
  49. package/src/components/VideoDetails/DeleteDialog.tsx +0 -148
  50. package/src/components/VideoDetails/VideoDetails.tsx +0 -358
  51. package/src/components/VideoDetails/VideoReferences.tsx +0 -63
  52. package/src/components/VideoDetails/useVideoDetails.ts +0 -103
  53. package/src/components/VideoInBrowser.tsx +0 -245
  54. package/src/components/VideoMetadata.tsx +0 -45
  55. package/src/components/VideoPlayer.tsx +0 -241
  56. package/src/components/VideoThumbnail.tsx +0 -139
  57. package/src/components/VideosBrowser.tsx +0 -100
  58. package/src/components/documentPreview/DocumentPreview.tsx +0 -84
  59. package/src/components/documentPreview/DraftStatus.tsx +0 -34
  60. package/src/components/documentPreview/MissingSchemaType.tsx +0 -32
  61. package/src/components/documentPreview/PaneItemPreview.tsx +0 -67
  62. package/src/components/documentPreview/PublishedStatus.tsx +0 -35
  63. package/src/components/documentPreview/TimeAgo.tsx +0 -12
  64. package/src/components/icons/Audio.tsx +0 -13
  65. package/src/components/icons/Resolution.tsx +0 -12
  66. package/src/components/icons/StopWatch.tsx +0 -20
  67. package/src/components/icons/ToolIcon.tsx +0 -19
  68. package/src/components/uploadConfiguration/PlaybackPolicy.tsx +0 -133
  69. package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +0 -76
  70. package/src/components/uploadConfiguration/PlaybackPolicyWarning.tsx +0 -29
  71. package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +0 -72
  72. package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +0 -180
  73. package/src/components/withFocusRing/helpers.ts +0 -24
  74. package/src/components/withFocusRing/index.ts +0 -1
  75. package/src/components/withFocusRing/withFocusRing.ts +0 -30
  76. package/src/context/DialogStateContext.tsx +0 -33
  77. package/src/context/DrmPlaybackWarningContext.tsx +0 -97
  78. package/src/hooks/useAccessControl.ts +0 -13
  79. package/src/hooks/useAssetDocumentValues.ts +0 -11
  80. package/src/hooks/useAssets.ts +0 -73
  81. package/src/hooks/useCancelUpload.ts +0 -22
  82. package/src/hooks/useClient.ts +0 -8
  83. package/src/hooks/useDialogState.ts +0 -11
  84. package/src/hooks/useDocReferences.ts +0 -21
  85. package/src/hooks/useFetchFileSize.ts +0 -55
  86. package/src/hooks/useImportMuxAssets.ts +0 -132
  87. package/src/hooks/useInView.ts +0 -41
  88. package/src/hooks/useMediaMetadata.ts +0 -104
  89. package/src/hooks/useMuxAssets.ts +0 -179
  90. package/src/hooks/useMuxPolling.ts +0 -52
  91. package/src/hooks/useResyncAsset.ts +0 -110
  92. package/src/hooks/useResyncMuxMetadata.ts +0 -169
  93. package/src/hooks/useSaveSecrets.ts +0 -78
  94. package/src/hooks/useSecretsDocumentValues.ts +0 -38
  95. package/src/hooks/useSecretsFormState.ts +0 -47
  96. package/src/plugin.tsx +0 -31
  97. package/src/sanity-ui.d.ts +0 -5
  98. package/src/schema.ts +0 -196
  99. package/src/util/addKeysToMuxData.ts +0 -30
  100. package/src/util/asserters.ts +0 -23
  101. package/src/util/assetTitlePlaceholder.ts +0 -31
  102. package/src/util/constants.ts +0 -15
  103. package/src/util/convertWatermarkToMux.ts +0 -160
  104. package/src/util/createSearchFilter.ts +0 -76
  105. package/src/util/createUrlParamsObject.ts +0 -29
  106. package/src/util/extractFiles.ts +0 -67
  107. package/src/util/formatBytes.ts +0 -31
  108. package/src/util/formatDriveShareLink.ts +0 -64
  109. package/src/util/formatSeconds.ts +0 -48
  110. package/src/util/generateJwt.ts +0 -57
  111. package/src/util/getAnimatedPosterSrc.ts +0 -26
  112. package/src/util/getPlaybackPolicy.ts +0 -69
  113. package/src/util/getPosterSrc.ts +0 -28
  114. package/src/util/getVideoMetadata.ts +0 -23
  115. package/src/util/getVideoSrc.ts +0 -23
  116. package/src/util/parsers.ts +0 -5
  117. package/src/util/pluginVersion.ts +0 -5
  118. package/src/util/readSecrets.ts +0 -38
  119. package/src/util/roundPxString.ts +0 -16
  120. package/src/util/textTracks.ts +0 -222
  121. package/src/util/tryWithSuspend.ts +0 -22
  122. package/src/util/types.ts +0 -566
  123. package/v2-incompatible.js +0 -11
@@ -1,160 +0,0 @@
1
- import {roundPxString} from './roundPxString'
2
- import type {MuxOverlaySettings, WatermarkConfig} from './types'
3
-
4
- /**
5
- * Converts a draggable watermark position (x, y percentages) to Mux's overlay_settings format.
6
- *
7
- * @param watermark - The watermark configuration with position, size, and opacity
8
- * @returns Mux overlay_settings object
9
- * @see {@link https://www.mux.com/docs/guides/add-watermarks-to-your-videos}
10
- */
11
- export function convertWatermarkToMuxOverlay(
12
- watermark: WatermarkConfig,
13
- options?: {
14
- /**
15
- * Video aspect ratio (width / height). Needed for correct vertical positioning,
16
- * especially on vertical videos.
17
- */
18
- videoAspectRatio?: number
19
- /**
20
- * Unit to emit for margins/width when generating overlay_settings from Canvas mode.
21
- * - 'px' will generate pixel strings according to Mux's scaling rules:
22
- * values are applied as if the video were scaled to 1920x1080 (horizontal)
23
- * or 1080x1920 (vertical).
24
- * - '%' preserves existing behavior.
25
- */
26
- units?: '%' | 'px'
27
- },
28
- ): MuxOverlaySettings | null {
29
- if (!watermark.enabled || !watermark.imageUrl) {
30
- return null
31
- }
32
-
33
- const size = watermark.size || 20
34
- const opacity = watermark.opacity ?? 0.7
35
-
36
- /**
37
- * Convert a percentage to whole-pixel string, using Mux's base dimensions:
38
- * - Horizontal video: 1920x1080
39
- * - Vertical video: 1080x1920
40
- */
41
- const toPxString = (valuePercent: number, axis: 'x' | 'y') => {
42
- const videoAspectRatio = options?.videoAspectRatio ?? 16 / 9
43
- const isVertical = videoAspectRatio > 0 && videoAspectRatio < 1
44
- const baseW = isVertical ? 1080 : 1920
45
- const baseH = isVertical ? 1920 : 1080
46
- const base = axis === 'x' ? baseW : baseH
47
- const px = (valuePercent / 100) * base
48
- let rounded = Math.round(px)
49
- // Avoid sending 0px (and JS -0); keep sign for negative margins.
50
- if (rounded === 0) rounded = px < 0 ? -1 : 1
51
- return `${rounded}px`
52
- }
53
-
54
- const normalizeToPixels = (value: string | undefined, axis: 'x' | 'y'): string | undefined => {
55
- if (!value) return value
56
- const trimmed = value.trim()
57
- if (trimmed.endsWith('px')) {
58
- return roundPxString(trimmed)
59
- }
60
- if (trimmed.endsWith('%')) {
61
- const n = Number(trimmed.slice(0, -1))
62
- if (!Number.isFinite(n)) return value
63
- return toPxString(n, axis)
64
- }
65
- return value
66
- }
67
-
68
- // If user provided explicit overlay settings, use them (Mux-documented format).
69
- // When `options.units === 'px'`, we normalize both % and px to whole-pixel strings,
70
- // honoring vertical vs horizontal video bases.
71
- if (watermark.overlay_settings) {
72
- const widthValue = watermark.overlay_settings.width
73
- const widthNormalized =
74
- options?.units === 'px' ? normalizeToPixels(widthValue, 'x') : widthValue
75
- return {
76
- ...watermark.overlay_settings,
77
- horizontal_margin:
78
- options?.units === 'px'
79
- ? (normalizeToPixels(watermark.overlay_settings.horizontal_margin, 'x') ??
80
- watermark.overlay_settings.horizontal_margin)
81
- : watermark.overlay_settings.horizontal_margin,
82
- vertical_margin:
83
- options?.units === 'px'
84
- ? (normalizeToPixels(watermark.overlay_settings.vertical_margin, 'y') ??
85
- watermark.overlay_settings.vertical_margin)
86
- : watermark.overlay_settings.vertical_margin,
87
- width: widthNormalized ?? `${size}%`,
88
- opacity: watermark.overlay_settings.opacity ?? `${Math.round(opacity * 100)}%`,
89
- }
90
- }
91
-
92
- const position = watermark.position || {x: 50, y: 50}
93
-
94
- /**
95
- * Our UI stores watermark position as the *center point* in percentages.
96
- * Mux margins are interpreted relative to an *edge* (based on align).
97
- *
98
- * To make "corner" placements match what the user dragged, we convert from
99
- * center-position to top-left margins by subtracting half the watermark size.
100
- *
101
- * Note: `size` is a percentage of video width. Mux `width` is also expressed
102
- * as a percentage of the video width, so we can reuse it for horizontal math.
103
- * For vertical math, we approximate using the same percentage to keep behavior
104
- * consistent with the current draggable UI (which also uses `size` in both axes
105
- * for bounds).
106
- */
107
- // Allow negative margins to compensate for rounding / letterboxing edge-cases.
108
- // We still clamp to a sane range so values don't explode.
109
- const clampPercent = (value: number) => Math.max(-100, Math.min(100, value))
110
-
111
- // Mux accepts percentage strings; avoid sending an exact "0%" by nudging to 0.01%.
112
- // This also handles the JS -0 edge case and tiny floating point remnants.
113
- const toPercentString = (value: number) => {
114
- const epsilon = 1e-9
115
- const isZeroish = value === 0 || Object.is(value, -0) || Math.abs(value) < epsilon
116
- return `${isZeroish ? 0.01 : value}%`
117
- }
118
-
119
- const watermarkWidthPercentOfVideoWidth = size
120
-
121
- /**
122
- * Convert watermark height into % of video height.
123
- * height% = (watermarkWidthPx / imageAspectRatio) / videoHeightPx
124
- * = (size% * videoWidthPx / imageAspectRatio) / videoHeightPx
125
- * = size% * (videoWidthPx/videoHeightPx) / imageAspectRatio
126
- * = size% * videoAspectRatio / imageAspectRatio
127
- */
128
- const videoAspectRatio = options?.videoAspectRatio ?? 16 / 9
129
- const imageAspectRatio = watermark.imageAspectRatio ?? 1
130
- const watermarkHeightPercentOfVideoHeight = Math.max(
131
- 0,
132
- Math.min(100, (size * videoAspectRatio) / imageAspectRatio),
133
- )
134
-
135
- const halfWidth = watermarkWidthPercentOfVideoWidth / 2
136
- const halfHeight = watermarkHeightPercentOfVideoHeight / 2
137
-
138
- const leftMargin = clampPercent(
139
- Math.min(position.x - halfWidth, 100 - watermarkWidthPercentOfVideoWidth),
140
- )
141
- const topMargin = clampPercent(
142
- Math.min(position.y - halfHeight, 100 - watermarkHeightPercentOfVideoHeight),
143
- )
144
-
145
- const units = options?.units ?? '%'
146
- const marginX = units === 'px' ? toPxString(leftMargin, 'x') : toPercentString(leftMargin)
147
- const marginY = units === 'px' ? toPxString(topMargin, 'y') : toPercentString(topMargin)
148
- const width = units === 'px' ? toPxString(size, 'x') : `${size}%`
149
-
150
- const overlaySettings: MuxOverlaySettings = {
151
- vertical_align: 'top',
152
- vertical_margin: marginY,
153
- horizontal_align: 'left',
154
- horizontal_margin: marginX,
155
- width,
156
- opacity: `${Math.round(opacity * 100)}%`,
157
- }
158
-
159
- return overlaySettings
160
- }
@@ -1,76 +0,0 @@
1
- // Adaptation of Sanity's createSearchQuery for our limited use case:
2
- // https://github.com/sanity-io/sanity/blob/next/packages/sanity/src/core/search/weighted/createSearchQuery.ts
3
- import {compact, toLower, trim, uniq, words} from 'lodash'
4
-
5
- const SPECIAL_CHARS = /([^!@#$%^&*(),\\/?";:{}|[\]+<>\s-])+/g
6
- const STRIP_EDGE_CHARS = /(^[.]+)|([.]+$)/
7
-
8
- function tokenize(string: string): string[] {
9
- return (string.match(SPECIAL_CHARS) || []).map((token) => token.replace(STRIP_EDGE_CHARS, ''))
10
- }
11
-
12
- function toGroqParams(terms: string[]): Record<string, string> {
13
- const params: Record<string, string> = {}
14
- return terms.reduce((acc, term, i) => {
15
- acc[`t${i}`] = `*${term}*` // "t" is short for term
16
- return acc
17
- }, params)
18
- }
19
-
20
- /**
21
- * Convert a string into an array of tokenized terms.
22
- *
23
- * Any (multi word) text wrapped in double quotes will be treated as "phrases", or separate tokens that
24
- * will not have its special characters removed.
25
- * E.g.`"the" "fantastic mr" fox fox book` =\> ["the", `"fantastic mr"`, "fox", "book"]
26
- *
27
- * Phrases wrapped in quotes are assigned relevance scoring differently from regular words.
28
- *
29
- * @internal
30
- */
31
- function extractTermsFromQuery(query: string): string[] {
32
- const quotedQueries = [] as string[]
33
- const unquotedQuery = query.replace(/("[^"]*")/g, (match) => {
34
- if (words(match).length > 1) {
35
- quotedQueries.push(match)
36
- return ''
37
- }
38
- return match
39
- })
40
-
41
- // Lowercase and trim quoted queries
42
- const quotedTerms = quotedQueries.map((str) => trim(toLower(str)))
43
-
44
- /**
45
- * Convert (remaining) search query into an array of deduped, sanitized tokens.
46
- * All white space and special characters are removed.
47
- * e.g. "The saint of Saint-Germain-des-Prés" =\> ['the', 'saint', 'of', 'germain', 'des', 'pres']
48
- */
49
- const remainingTerms = uniq(compact(tokenize(toLower(unquotedQuery))))
50
-
51
- return [...quotedTerms, ...remainingTerms]
52
- }
53
-
54
- /**
55
- * Create GROQ constraints, given search terms and the full spec of available document types and fields.
56
- * Essentially a large list of all possible fields (joined by logical OR) to match our search terms against.
57
- */
58
- function createConstraints(terms: string[], includeAssetId: boolean) {
59
- const searchPaths = includeAssetId ? ['filename', 'assetId'] : ['filename']
60
- const constraints = terms
61
- .map((_term, i) => searchPaths.map((joinedPath) => `${joinedPath} match $t${i}`))
62
- .filter((constraint) => constraint.length > 0)
63
-
64
- return constraints.map((constraint) => `(${constraint.join(' || ')})`)
65
- }
66
-
67
- export function createSearchFilter(query: string) {
68
- const terms = extractTermsFromQuery(query)
69
-
70
- return {
71
- filter: createConstraints(terms, query.length >= 8), // if the search is big enough, include the assetId (mux id) in the results
72
- params: {
73
- ...toGroqParams(terms),
74
- },
75
- }
76
- }
@@ -1,29 +0,0 @@
1
- import type {SanityClient} from 'sanity'
2
-
3
- import {getPlaybackId} from '../util/getPlaybackPolicy'
4
- import {type Audience, generateJwt} from './generateJwt'
5
- import {getPlaybackPolicyById} from './getPlaybackPolicy'
6
- import type {AssetThumbnailOptions} from './types'
7
-
8
- /**
9
- * May throw a Promise. Call this with {@link tryWithSuspend} or rethrow the Promise
10
- */
11
- export function createUrlParamsObject(
12
- client: SanityClient,
13
- asset: AssetThumbnailOptions['asset'],
14
- params: object,
15
- audience: Audience,
16
- ) {
17
- const playbackId = getPlaybackId(asset)
18
-
19
- let searchParams = new URLSearchParams(
20
- JSON.parse(JSON.stringify(params, (_, v) => v ?? undefined)),
21
- )
22
- const playbackPolicy = getPlaybackPolicyById(asset, playbackId)?.policy
23
- if (playbackPolicy === 'signed' || playbackPolicy === 'drm') {
24
- const token = generateJwt(client, playbackId, audience, params)
25
- searchParams = new URLSearchParams({token})
26
- }
27
-
28
- return {playbackId, searchParams}
29
- }
@@ -1,67 +0,0 @@
1
- /**
2
- * Utilities for extracting files from dataTransfer in a predictable cross-browser fashion.
3
- * Also recursively extracts files from a directory
4
- * Inspired by https://github.com/component/normalized-upload
5
- */
6
-
7
- export function extractDroppedFiles(dataTransfer: DataTransfer) {
8
- const files = Array.from(dataTransfer.files || [])
9
- const items = Array.from(dataTransfer.items || [])
10
- if (files && files.length > 0) {
11
- return Promise.resolve(files)
12
- }
13
- return normalizeItems(items).then((arr) => arr.flat())
14
- }
15
-
16
- function normalizeItems(items: DataTransferItem[]) {
17
- return Promise.all(
18
- items.map((item) => {
19
- // directory
20
- if (item.kind === 'file' && item.webkitGetAsEntry) {
21
- let entry: FileSystemEntry | File[] | null
22
- // Edge throws
23
- try {
24
- entry = item.webkitGetAsEntry()
25
- } catch {
26
- return [item.getAsFile()]
27
- }
28
- if (!entry) {
29
- return []
30
- }
31
- return entry.isDirectory ? walk(entry) : [item.getAsFile()]
32
- }
33
-
34
- // file
35
- if (item.kind === 'file') {
36
- const file = item.getAsFile()
37
- return Promise.resolve(file ? [file] : [])
38
- }
39
-
40
- // others
41
- return new Promise((resolve) => item.getAsString(resolve)).then((str?: any) =>
42
- str ? [new File([str], 'unknown.txt', {type: item.type})] : [],
43
- )
44
- }),
45
- )
46
- }
47
-
48
- function isFile(entry: FileSystemEntry): entry is FileSystemFileEntry {
49
- return entry.isFile
50
- }
51
- function isDirectory(entry: FileSystemEntry): entry is FileSystemDirectoryEntry {
52
- return entry.isDirectory
53
- }
54
-
55
- function walk(entry: FileSystemEntry): any {
56
- if (isFile(entry)) {
57
- return new Promise((resolve) => entry.file(resolve)).then((file) => [file])
58
- }
59
-
60
- if (isDirectory(entry)) {
61
- const dir = entry.createReader()
62
- return new Promise<any>((resolve) => dir.readEntries(resolve))
63
- .then((entries: FileSystemEntry[]) => entries.filter((entr) => !entr.name.startsWith('.')))
64
- .then((entries) => Promise.all(entries.map(walk)).then((arr) => arr.flat()))
65
- }
66
- return Promise.resolve([])
67
- }
@@ -1,31 +0,0 @@
1
- // From: https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
2
- /**
3
- * Format bytes as human-readable text.
4
- *
5
- * @param bytes Number of bytes.
6
- * @param si True to use metric (SI) units, aka powers of 1000. False to use
7
- * binary (IEC), aka powers of 1024.
8
- * @param dp Number of decimal places to display.
9
- *
10
- * @return Formatted string.
11
- */
12
- export default function formatBytes(bytes: number, si = false, dp = 1) {
13
- const thresh = si ? 1000 : 1024
14
-
15
- if (Math.abs(bytes) < thresh) {
16
- return bytes + ' B'
17
- }
18
-
19
- const units = si
20
- ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
21
- : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
22
- let u = -1
23
- const r = 10 ** dp
24
-
25
- do {
26
- bytes /= thresh
27
- ++u
28
- } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1)
29
-
30
- return bytes.toFixed(dp) + ' ' + units[u]
31
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * Format Google Drive share links as Google Drive export links.
3
- * Supported formats:
4
- * - https://drive.google.com/uc?id=<ID>...
5
- * - https://drive.google.com/open?id=<ID>...
6
- * - https://drive.google.com/file/d/<ID>...
7
- * - https://drive.google.com/folder/<ID>...
8
- *
9
- * @param url Google Drive share link to format.
10
- * @returns Google Drive export link (URL passthrough if not share link).
11
- */
12
- export function formatDriveShareLink(url: string): string {
13
- // Export link formatter.
14
- const formatExportLink = (id: string) => {
15
- return `https://drive.google.com/uc?export=download&id=${id}`
16
- }
17
-
18
- // URL formatting.
19
- try {
20
- // Parse URL.
21
- const trimmed = url.trim()
22
- const parsed = new URL(trimmed)
23
-
24
- // Enforce strict host name.
25
- if (parsed.hostname !== 'drive.google.com') {
26
- throw new Error('URL is not from Google Drive.')
27
- }
28
-
29
- // Look for ID in search parameters.
30
- const id = parsed.searchParams.get('id') || ''
31
- if (id.length) {
32
- return formatExportLink(id)
33
- }
34
-
35
- // Look for ID in path name.
36
- const path = parsed.pathname.split('/') || []
37
-
38
- // Path is /file/d/<ID>...
39
- if (path.includes('file') && path.includes('d')) {
40
- const index =
41
- path.findIndex((value: string) => {
42
- return value === 'd'
43
- }) + 1
44
- const fileId = path.at(index) || ''
45
- return formatExportLink(fileId)
46
- }
47
-
48
- // Path is /folder/<ID>...
49
- if (path.includes('folders')) {
50
- const index =
51
- path.findIndex((value: string) => {
52
- return value === 'folders'
53
- }) + 1
54
- const folderId = path.at(index) || ''
55
- return formatExportLink(folderId)
56
- }
57
-
58
- // URL not recognized.
59
- throw new Error('URL was not recognized.')
60
- } catch {
61
- // URL passthrough by default.
62
- return url
63
- }
64
- }
@@ -1,48 +0,0 @@
1
- // From: https://stackoverflow.com/a/11486026/10433647
2
- export function formatSeconds(seconds: number): string {
3
- if (typeof seconds !== 'number' || Number.isNaN(seconds)) {
4
- return ''
5
- }
6
- // Hours, minutes and seconds
7
- const hrs = ~~(seconds / 3600)
8
- const mins = ~~((seconds % 3600) / 60)
9
- const secs = ~~seconds % 60
10
-
11
- // Output like "1:01" or "4:03:59" or "123:03:59"
12
- let ret = ''
13
-
14
- if (hrs > 0) {
15
- ret += '' + hrs + ':' + (mins < 10 ? '0' : '')
16
- }
17
-
18
- ret += '' + mins + ':' + (secs < 10 ? '0' : '')
19
- ret += '' + secs
20
- return ret
21
- }
22
-
23
- // Output like "05:14:01"
24
- export function formatSecondsToHHMMSS(seconds: number): string {
25
- const hrs = Math.floor(seconds / 3600)
26
- .toString()
27
- .padStart(2, '0')
28
- const mins = Math.floor((seconds % 3600) / 60)
29
- .toString()
30
- .padStart(2, '0')
31
- const secs = Math.floor(seconds % 60)
32
- .toString()
33
- .padStart(2, '0')
34
-
35
- return `${hrs}:${mins}:${secs}`
36
- }
37
-
38
- // Checks if time has a HH:MM:SS format like "05:14:01"
39
- export function isValidTimeFormat(time: string) {
40
- const regex = /^([0-1]?[0-9]|2[0-3]):([0-5]?[0-9]):([0-5]?[0-9])$/
41
- return regex.test(time) || time === ''
42
- }
43
-
44
- // Converts a time like "05:14:01" to seconds
45
- export function getSecondsFromTimeFormat(time: string): number {
46
- const [hh = 0, mm = 0, ss = 0] = time.split(':').map(Number)
47
- return hh * 3600 + mm * 60 + ss
48
- }
@@ -1,57 +0,0 @@
1
- import type {SanityClient} from 'sanity'
2
- import {suspend} from 'suspend-react'
3
-
4
- import {readSecrets} from './readSecrets'
5
- import type {AnimatedThumbnailOptions, ThumbnailOptions} from './types'
6
-
7
- export type Audience = 'g' | 's' | 't' | 'v' | 'd'
8
-
9
- export type Payload<T extends Audience> = T extends 'g'
10
- ? AnimatedThumbnailOptions
11
- : T extends 's'
12
- ? never
13
- : T extends 't'
14
- ? ThumbnailOptions
15
- : T extends 'v'
16
- ? never
17
- : T extends 'd'
18
- ? never
19
- : never
20
-
21
- /**
22
- * Uses suspend. Call this with {@link tryWithSuspend} or rethrow the Promise
23
- */
24
- export function generateJwt<T extends Audience>(
25
- client: SanityClient,
26
- playbackId: string,
27
- aud: T,
28
- payload?: Payload<T>,
29
- ): string {
30
- const {signingKeyId, signingKeyPrivate} = readSecrets(client)
31
- if (!signingKeyId) {
32
- throw new TypeError("Missing `signingKeyId`.\n Check your plugin's configuration")
33
- }
34
- if (!signingKeyPrivate) {
35
- throw new TypeError("Missing `signingKeyPrivate`.\n Check your plugin's configuration")
36
- }
37
-
38
- /* Using suspend means we need to use Suspense on parent components.
39
- Also, this will throw a Promise under the hood (apparently common in React),
40
- so if we want to catch errors we have to take this into account in catch blocks
41
- and rethrow promises. */
42
- // @ts-expect-error - handle missing typings for this package
43
- const {default: sign} = suspend(() => import('jsonwebtoken-esm/sign'), ['jsonwebtoken-esm/sign'])
44
-
45
- return sign(
46
- payload ? JSON.parse(JSON.stringify(payload, (_, v) => v ?? undefined)) : {},
47
- atob(signingKeyPrivate),
48
- {
49
- algorithm: 'RS256',
50
- keyid: signingKeyId,
51
- audience: aud,
52
- subject: playbackId,
53
- noTimestamp: true,
54
- expiresIn: '12h',
55
- },
56
- )
57
- }
@@ -1,26 +0,0 @@
1
- import type {SanityClient} from 'sanity'
2
-
3
- import {createUrlParamsObject} from './createUrlParamsObject'
4
- import type {AnimatedThumbnailOptions, MuxAnimatedThumbnailUrl} from './types'
5
- import {type AssetThumbnailOptions} from './types'
6
-
7
- export interface AnimatedPosterSrcOptions extends AnimatedThumbnailOptions {
8
- asset: AssetThumbnailOptions['asset']
9
- client: SanityClient
10
- }
11
-
12
- export function getAnimatedPosterSrc({
13
- asset,
14
- client,
15
- height,
16
- width,
17
- start = asset.thumbTime ? Math.max(0, asset.thumbTime - 2.5) : 0,
18
- end = start + 5,
19
- fps = 15,
20
- }: AnimatedPosterSrcOptions): MuxAnimatedThumbnailUrl {
21
- const params = {height, width, start, end, fps}
22
-
23
- const {playbackId, searchParams} = createUrlParamsObject(client, asset, params, 'g')
24
-
25
- return `https://image.mux.com/${playbackId}/animated.gif?${searchParams}`
26
- }
@@ -1,69 +0,0 @@
1
- import type {
2
- AdvancedPlaybackPolicy,
3
- MuxPlaybackId,
4
- PlaybackPolicy,
5
- VideoAssetDocument,
6
- } from './types'
7
-
8
- /* - Returns the playback id of the asset based on the specified priority.
9
- By default chooses the "strongest" policy
10
- - Otherwise, returns the first playback id in the array.
11
- */
12
- export function getPlaybackId(
13
- asset: Pick<VideoAssetDocument, 'data'>,
14
- priority: string[] = ['drm', 'signed', 'public'],
15
- ): string {
16
- try {
17
- if (!asset) {
18
- throw new TypeError('Tried to get playback Id with no asset')
19
- }
20
-
21
- const playbackIds = asset.data?.playback_ids
22
- if (playbackIds && playbackIds.length > 0) {
23
- for (const policy of priority) {
24
- const match = playbackIds.find((entry) => entry.policy === policy)
25
- if (match) {
26
- return match.id
27
- }
28
- }
29
-
30
- return playbackIds[0]!.id
31
- }
32
-
33
- throw new TypeError('Missing playbackId')
34
- } catch (e) {
35
- console.error('Asset is missing a playbackId', {asset}, e)
36
- throw e
37
- }
38
- }
39
-
40
- export function getPlaybackPolicy(
41
- asset: Pick<VideoAssetDocument, 'data' | 'playbackId'>,
42
- ): MuxPlaybackId | undefined {
43
- return (
44
- asset.data?.playback_ids?.find(
45
- (playbackId) => getPlaybackId(asset, ['drm', 'signed', 'public']) === playbackId.id,
46
- ) ?? {id: '', policy: 'public'}
47
- )
48
- }
49
-
50
- export function getPlaybackPolicyById(
51
- asset: Pick<VideoAssetDocument, 'data'>,
52
- playbackId: string,
53
- ): MuxPlaybackId | undefined {
54
- return asset.data?.playback_ids?.find((entry) => playbackId === entry.id)
55
- }
56
-
57
- export function hasPlaybackPolicy(
58
- data: Partial<{
59
- playback_policy?: PlaybackPolicy[]
60
- advanced_playback_policies: AdvancedPlaybackPolicy[]
61
- }>,
62
- policy: PlaybackPolicy,
63
- ) {
64
- return (
65
- (data.advanced_playback_policies &&
66
- data.advanced_playback_policies.find((p) => p.policy === policy)) ||
67
- data.playback_policy?.find((p) => p === policy)
68
- )
69
- }
@@ -1,28 +0,0 @@
1
- import type {SanityClient} from 'sanity'
2
-
3
- import {createUrlParamsObject} from './createUrlParamsObject'
4
- import type {MuxThumbnailUrl, ThumbnailOptions} from './types'
5
- import {type AssetThumbnailOptions} from './types'
6
-
7
- export interface PosterSrcOptions extends ThumbnailOptions {
8
- asset: AssetThumbnailOptions['asset']
9
- client: SanityClient
10
- }
11
-
12
- export function getPosterSrc({
13
- asset,
14
- client,
15
- fit_mode,
16
- height,
17
- time = asset.thumbTime ?? undefined,
18
- width,
19
- }: PosterSrcOptions): MuxThumbnailUrl {
20
- const params = {fit_mode, height, width}
21
- if (time !== undefined) {
22
- ;(params as any).time = time
23
- }
24
-
25
- const {playbackId, searchParams} = createUrlParamsObject(client, asset, params, 't')
26
-
27
- return `https://image.mux.com/${playbackId}/thumbnail.png?${searchParams}`
28
- }
@@ -1,23 +0,0 @@
1
- import {formatSeconds} from './formatSeconds'
2
- import type {MuxTextTrack, VideoAssetDocument} from './types'
3
-
4
- export default function getVideoMetadata(doc: VideoAssetDocument) {
5
- const id = doc.assetId || doc._id || ''
6
- const date = doc.data?.created_at
7
- ? new Date(Number(doc.data.created_at) * 1000)
8
- : new Date(doc._createdAt || doc._updatedAt || Date.now())
9
-
10
- return {
11
- title: doc.filename || id.slice(0, 12),
12
- id: id,
13
- playbackId: doc.playbackId,
14
- createdAt: date,
15
- duration: doc.data?.duration ? formatSeconds(doc.data?.duration) : undefined,
16
- playback_ids: doc.data?.playback_ids,
17
- aspect_ratio: doc.data?.aspect_ratio,
18
- max_stored_resolution: doc.data?.max_stored_resolution,
19
- max_stored_frame_rate: doc.data?.max_stored_frame_rate,
20
- text_tracks:
21
- doc.data?.tracks?.filter((track): track is MuxTextTrack => track.type === 'text') || [],
22
- }
23
- }