sanity-plugin-mux-input 2.16.0 → 2.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-mux-input",
3
- "version": "2.16.0",
3
+ "version": "2.18.0",
4
4
  "description": "An input component that integrates Sanity Studio with Mux video encoding/hosting service.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -4,10 +4,39 @@ import {catchError, mergeMap, mergeMapTo, switchMap} from 'rxjs/operators'
4
4
  import type {SanityClient} from 'sanity'
5
5
 
6
6
  import {createUpChunkObservable} from '../clients/upChunkObservable'
7
- import type {MuxAsset, MuxNewAssetSettings} from '../util/types'
7
+ import {formatDriveShareLink} from '../util/formatDriveShareLink'
8
+ import {roundPxString} from '../util/roundPxString'
9
+ import type {MuxAsset, MuxNewAssetSettings, WatermarkConfig} from '../util/types'
8
10
  import {getAsset} from './assets'
9
11
  import {testSecretsObservable} from './secrets'
10
12
 
13
+ function sanitizeOverlaySettingsInPlace(settings: MuxNewAssetSettings) {
14
+ const inputs = settings.input
15
+ if (!inputs) return
16
+ for (const input of inputs) {
17
+ const overlay = (input as {overlay_settings?: Record<string, unknown>}).overlay_settings
18
+ if (!overlay) continue
19
+
20
+ const hm = roundPxString(overlay.horizontal_margin)
21
+ const vm = roundPxString(overlay.vertical_margin)
22
+ const w = roundPxString(overlay.width)
23
+
24
+ if (hm) overlay.horizontal_margin = hm
25
+ if (vm) overlay.vertical_margin = vm
26
+ if (w) overlay.width = w
27
+ }
28
+ }
29
+
30
+ function sanitizePxStringsInJson(json: string): string {
31
+ return json.replace(/"(-?\d+(?:\.\d+)?)px"/g, (_match, num) => {
32
+ const n = Number(num)
33
+ if (!Number.isFinite(n)) return _match
34
+ let rounded = Math.round(n)
35
+ if (rounded === 0) rounded = n < 0 ? -1 : 1
36
+ return `"${rounded}px"`
37
+ })
38
+ }
39
+
11
40
  export function cancelUpload(client: SanityClient, uuid: string) {
12
41
  return client.observable.request({
13
42
  url: `/addons/mux/uploads/${client.config().dataset}/${uuid}`,
@@ -20,10 +49,12 @@ export function uploadUrl({
20
49
  url,
21
50
  settings,
22
51
  client,
52
+ watermark,
23
53
  }: {
24
54
  url: string
25
55
  settings: MuxNewAssetSettings
26
56
  client: SanityClient
57
+ watermark?: WatermarkConfig
27
58
  }) {
28
59
  return testUrl(url).pipe(
29
60
  switchMap((validUrl) => {
@@ -38,9 +69,10 @@ export function uploadUrl({
38
69
  const muxBody = settings
39
70
  if (!muxBody.input) muxBody.input = [{type: 'video'}]
40
71
  muxBody.input[0].url = validUrl
72
+ sanitizeOverlaySettingsInPlace(muxBody)
41
73
 
42
74
  const query = {
43
- muxBody: JSON.stringify(muxBody),
75
+ muxBody: sanitizePxStringsInJson(JSON.stringify(muxBody)),
44
76
  filename: validUrl.split('/').slice(-1)[0],
45
77
  }
46
78
 
@@ -79,10 +111,12 @@ export function uploadFile({
79
111
  settings,
80
112
  client,
81
113
  file,
114
+ watermark,
82
115
  }: {
83
116
  settings: MuxNewAssetSettings
84
117
  client: SanityClient
85
118
  file: File
119
+ watermark?: WatermarkConfig
86
120
  }) {
87
121
  return testFile(file).pipe(
88
122
  switchMap((fileOptions) => {
@@ -95,6 +129,7 @@ export function uploadFile({
95
129
  }
96
130
  const uuid = generateUuid()
97
131
  const body = settings
132
+ sanitizeOverlaySettingsInPlace(body)
98
133
 
99
134
  return concat(
100
135
  of({type: 'uuid' as const, uuid}),
@@ -129,7 +164,7 @@ export function uploadFile({
129
164
  if (event.type !== 'success') {
130
165
  return of(event)
131
166
  }
132
- return from(updateAssetDocumentFromUpload(client, uuid)).pipe(
167
+ return from(updateAssetDocumentFromUpload(client, uuid, watermark)).pipe(
133
168
  // eslint-disable-next-line max-nested-callbacks
134
169
  mergeMap((doc) => of({...event, asset: doc}))
135
170
  )
@@ -201,7 +236,11 @@ function pollUpload(client: SanityClient, uuid: string): Promise<UploadResponse>
201
236
  })
202
237
  }
203
238
 
204
- async function updateAssetDocumentFromUpload(client: SanityClient, uuid: string) {
239
+ async function updateAssetDocumentFromUpload(
240
+ client: SanityClient,
241
+ uuid: string,
242
+ _watermark?: WatermarkConfig
243
+ ) {
205
244
  let upload: UploadResponse
206
245
  let asset: {data: MuxAsset}
207
246
  try {
@@ -242,17 +281,18 @@ export function testUrl(url: string): Observable<string> {
242
281
  if (typeof url !== 'string') {
243
282
  return throwError(error)
244
283
  }
245
- const trimmedUrl = url.trim()
284
+ let formattedUrl = url.trim()
285
+ formattedUrl = formatDriveShareLink(formattedUrl)
246
286
  let parsed
247
287
  try {
248
- parsed = new URL(trimmedUrl)
288
+ parsed = new URL(formattedUrl)
249
289
  } catch (err) {
250
290
  return throwError(error)
251
291
  }
252
292
  if (parsed && !parsed.protocol.match(/http:|https:/)) {
253
293
  return throwError(error)
254
294
  }
255
- return of(trimmedUrl)
295
+ return of(formattedUrl)
256
296
  }
257
297
 
258
298
  function optionsFromFile(opts: {preserveFilename?: boolean}, file: File) {
@@ -18,6 +18,7 @@ import {useId, useRef, useState} from 'react'
18
18
 
19
19
  import {addTextTrackFromUrl, generateSubtitles, getAsset} from '../actions/assets'
20
20
  import {useClient} from '../hooks/useClient'
21
+ import {addKeysToMuxData} from '../util/addKeysToMuxData'
21
22
  import {extractErrorMessage, pollTrackStatus} from '../util/textTracks'
22
23
  import {type MuxTextTrack, SUPPORTED_MUX_LANGUAGES, type VideoAssetDocument} from '../util/types'
23
24
 
@@ -60,6 +61,20 @@ export default function AddCaptionDialog({asset, onAdd, onClose}: Props) {
60
61
  return assetDocument.url
61
62
  }
62
63
 
64
+ const refreshAssetData = async () => {
65
+ if (!asset._id || !asset.assetId) return
66
+ try {
67
+ const latestAssetData = await getAsset(client, asset.assetId)
68
+ const dataWithKeys = addKeysToMuxData(latestAssetData.data)
69
+ await client
70
+ .patch(asset._id)
71
+ .set({data: dataWithKeys, status: latestAssetData.data.status})
72
+ .commit({returnDocuments: false})
73
+ } catch (refreshError) {
74
+ console.error('Failed to refresh asset data:', refreshError)
75
+ }
76
+ }
77
+
63
78
  const handleAddTrackFromUrl = async () => {
64
79
  if (!asset.assetId) {
65
80
  throw new Error('Asset ID is required')
@@ -75,9 +90,9 @@ export default function AddCaptionDialog({asset, onAdd, onClose}: Props) {
75
90
  vttUrlToUse = await uploadVttFile(selectedFile)
76
91
  } catch (uploadError) {
77
92
  toast.push({
78
- title: 'Failed to upload VTT file',
93
+ title: 'Failed to upload caption file',
79
94
  status: 'error',
80
- description: 'Could not upload the VTT file to Sanity. Please try again.',
95
+ description: 'Could not upload the caption file to Sanity. Please try again.',
81
96
  })
82
97
  setIsSubmitting(false)
83
98
  throw uploadError
@@ -95,7 +110,7 @@ export default function AddCaptionDialog({asset, onAdd, onClose}: Props) {
95
110
  assetId: asset.assetId,
96
111
  trackName: trimmedName,
97
112
  trackLanguageCode: trimmedLanguageCode,
98
- onTrackErrored: (track) => {
113
+ onTrackErrored: async (track) => {
99
114
  const errorMessage =
100
115
  track.error?.messages?.[0] ||
101
116
  track.error?.type ||
@@ -105,6 +120,7 @@ export default function AddCaptionDialog({asset, onAdd, onClose}: Props) {
105
120
  status: 'error',
106
121
  description: errorMessage,
107
122
  })
123
+ await refreshAssetData()
108
124
  onAdd(track)
109
125
  onClose()
110
126
  },
@@ -132,11 +148,13 @@ export default function AddCaptionDialog({asset, onAdd, onClose}: Props) {
132
148
  description:
133
149
  'The track was created and is being processed. It will appear in the list shortly.',
134
150
  })
151
+ await refreshAssetData()
135
152
  onAdd(result.track)
136
153
  onClose()
137
154
  return
138
155
  }
139
156
 
157
+ await refreshAssetData()
140
158
  toast.push({
141
159
  title: 'Caption track added',
142
160
  status: 'success',
@@ -195,9 +213,9 @@ export default function AddCaptionDialog({asset, onAdd, onClose}: Props) {
195
213
  if (!isAutogenerated) {
196
214
  if (!selectedFile && !vttUrl.trim()) {
197
215
  toast.push({
198
- title: 'VTT file or URL required',
216
+ title: 'Caption file or URL required',
199
217
  status: 'error',
200
- description: 'Please select a VTT file or enter a VTT file URL',
218
+ description: 'Please select a VTT or SRT file or enter a caption file URL',
201
219
  })
202
220
  return
203
221
  }
@@ -209,7 +227,8 @@ export default function AddCaptionDialog({asset, onAdd, onClose}: Props) {
209
227
  toast.push({
210
228
  title: 'Invalid URL',
211
229
  status: 'error',
212
- description: 'Please enter a valid URL (e.g., https://example.com/subtitles.vtt)',
230
+ description:
231
+ 'Please enter a valid URL (e.g., https://example.com/subtitles.vtt or subtitles.srt)',
213
232
  })
214
233
  return
215
234
  }
@@ -303,7 +322,7 @@ export default function AddCaptionDialog({asset, onAdd, onClose}: Props) {
303
322
  <input
304
323
  ref={fileInputRef}
305
324
  type="file"
306
- accept=".vtt,text/vtt"
325
+ accept=".vtt,text/vtt,.srt,application/x-subrip"
307
326
  style={{display: 'none'}}
308
327
  onChange={(e) => {
309
328
  if (e.target.files && e.target.files.length > 0 && !isSubmitting) {
@@ -314,10 +333,10 @@ export default function AddCaptionDialog({asset, onAdd, onClose}: Props) {
314
333
  />
315
334
  </Card>
316
335
  <Text size={1} muted style={{textAlign: 'center'}}>
317
- Or enter the VTT file URL
336
+ Or enter the caption file URL
318
337
  </Text>
319
338
  <Stack space={2}>
320
- <Label htmlFor="vtt-url">VTT File URL</Label>
339
+ <Label htmlFor="vtt-url">Caption File URL (.vtt or .srt)</Label>
321
340
  <TextInput
322
341
  id="vtt-url"
323
342
  placeholder="https://example.com/subtitles.vtt"