sanity-plugin-mux-input 2.11.2 → 2.12.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-mux-input",
3
- "version": "2.11.2",
3
+ "version": "2.12.1",
4
4
  "description": "An input component that integrates Sanity Studio with Mux video encoding/hosting service.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -90,7 +90,7 @@
90
90
  "eslint-plugin-simple-import-sort": "^12.1.1",
91
91
  "husky": "^9.0.11",
92
92
  "lint-staged": "^15.2.2",
93
- "npm-run-all2": "^5.0.2",
93
+ "npm-run-all2": "^6.2.6",
94
94
  "prettier": "^3.5.2",
95
95
  "prettier-plugin-packagejson": "^2.5.9",
96
96
  "react": "^19.0.0",
@@ -104,7 +104,7 @@
104
104
  "peerDependencies": {
105
105
  "react": "^18.3 || ^19",
106
106
  "react-is": "^18.3 || ^19",
107
- "sanity": "^3.42.0 || ^4.0.0-0",
107
+ "sanity": "^3.42.0 || ^4.0.0-0 || ^5.0.0",
108
108
  "styled-components": "^5 || ^6"
109
109
  },
110
110
  "engines": {
@@ -3,10 +3,11 @@ import {definePlugin} from 'sanity'
3
3
  import createStudioTool, {DEFAULT_TOOL_CONFIG} from '../components/StudioTool'
4
4
  import {muxVideoCustomRendering} from '../plugin'
5
5
  import {muxVideoSchema, schemaTypes} from '../schema'
6
- import type {PluginConfig} from '../util/types'
6
+ import type {PluginConfig, StaticRenditionResolution} from '../util/types'
7
7
  export type {VideoAssetDocument} from '../util/types'
8
8
 
9
9
  export const defaultConfig: PluginConfig = {
10
+ static_renditions: [],
10
11
  mp4_support: 'none',
11
12
  video_quality: 'plus',
12
13
  max_resolution_tier: '1080p',
@@ -17,6 +18,25 @@ export const defaultConfig: PluginConfig = {
17
18
  allowedRolesForConfiguration: [],
18
19
  }
19
20
 
21
+ /**
22
+ * Converts legacy mp4_support configuration to static_renditions format
23
+ */
24
+ function convertLegacyConfig(config: Partial<PluginConfig>): {
25
+ static_renditions: StaticRenditionResolution[]
26
+ } {
27
+ // If static_renditions is already provided, use it
28
+ if (config.static_renditions && config.static_renditions.length > 0) {
29
+ return {static_renditions: config.static_renditions}
30
+ }
31
+
32
+ // Convert legacy mp4_support to static_renditions
33
+ if (config.mp4_support === 'standard') {
34
+ return {static_renditions: ['highest']}
35
+ }
36
+
37
+ return {static_renditions: []}
38
+ }
39
+
20
40
  export const muxInput = definePlugin<Partial<PluginConfig> | void>((userConfig) => {
21
41
  // TODO: Remove this on next major version when we end support for encoding_tier
22
42
  if (typeof userConfig === 'object' && 'encoding_tier' in userConfig) {
@@ -30,7 +50,11 @@ export const muxInput = definePlugin<Partial<PluginConfig> | void>((userConfig)
30
50
  }
31
51
  }
32
52
  }
33
- const config: PluginConfig = {...defaultConfig, ...(userConfig || {})}
53
+ const config: PluginConfig = {
54
+ ...defaultConfig,
55
+ ...(userConfig || {}),
56
+ ...convertLegacyConfig(userConfig || {}),
57
+ }
34
58
  return {
35
59
  name: 'mux-input',
36
60
  schema: {
@@ -156,7 +156,7 @@ type UploadResponse = {
156
156
  cors_origin: string
157
157
  id: string
158
158
  new_asset_settings: {
159
- mp4_support: 'standard' | 'none'
159
+ static_renditions?: {resolution: string}[]
160
160
  passthrough: string
161
161
  playback_policies: ['public' | 'signed']
162
162
  }
@@ -33,14 +33,23 @@ const Player = ({asset, buttons, readOnly, onChange}: Props) => {
33
33
  return true
34
34
  }, [asset])
35
35
  const isPreparingStaticRenditions = useMemo<boolean>(() => {
36
- if (asset?.data?.static_renditions?.status === 'preparing') {
37
- return true
36
+ // Legacy: If static_renditions has a status field, it was created with mp4_support (deprecated)
37
+ // We don't process this old format, just return false
38
+ // Note: 'disabled' status is valid in the new format when no renditions were requested
39
+ if (
40
+ asset?.data?.static_renditions?.status &&
41
+ asset?.data?.static_renditions?.status !== 'disabled'
42
+ ) {
43
+ return false
38
44
  }
39
- if (asset?.data?.static_renditions?.status === 'ready') {
45
+
46
+ // Check if any file in static_renditions is still preparing
47
+ const files = asset?.data?.static_renditions?.files
48
+ if (!files || files.length === 0) {
40
49
  return false
41
50
  }
42
- return false
43
- }, [asset?.data?.static_renditions?.status])
51
+ return files.some((file) => file.status === 'preparing')
52
+ }, [asset?.data?.static_renditions?.status, asset?.data?.static_renditions?.files])
44
53
  const playRef = useRef<HTMLDivElement>(null)
45
54
  const muteRef = useRef<HTMLDivElement>(null)
46
55
  const handleCancelUpload = useCancelUpload(asset, onChange)
@@ -2,7 +2,7 @@ import {DocumentVideoIcon, UploadIcon} from '@sanity/icons'
2
2
  import {Box, Button, Card, Checkbox, Dialog, Flex, Label, Radio, Stack, Text} from '@sanity/ui'
3
3
  import {uuid} from '@sanity/uuid'
4
4
  import LanguagesList from 'iso-639-1'
5
- import {useEffect, useId, useReducer, useRef} from 'react'
5
+ import {useEffect, useId, useMemo, useReducer, useRef, useState} from 'react'
6
6
  import {FormField} from 'sanity'
7
7
 
8
8
  import formatBytes from '../util/formatBytes'
@@ -14,6 +14,7 @@ import {
14
14
  type MuxNewAssetSettings,
15
15
  type PluginConfig,
16
16
  type Secrets,
17
+ type StaticRenditionResolution,
17
18
  type SupportedMuxLanguage,
18
19
  type UploadConfig,
19
20
  type UploadTextTrack,
@@ -25,7 +26,7 @@ import type {StagedUpload} from './Uploader'
25
26
  export type UploadConfigurationStateAction =
26
27
  | {action: 'video_quality'; value: UploadConfig['video_quality']}
27
28
  | {action: 'max_resolution_tier'; value: UploadConfig['max_resolution_tier']}
28
- | {action: 'mp4_support'; value: UploadConfig['mp4_support']}
29
+ | {action: 'static_renditions'; value: UploadConfig['static_renditions']}
29
30
  | {action: 'normalize_audio'; value: UploadConfig['normalize_audio']}
30
31
  | {action: 'signed_policy'; value: UploadConfig['signed_policy']}
31
32
  | {action: 'public_policy'; value: UploadConfig['public_policy']}
@@ -43,6 +44,34 @@ const RESOLUTION_TIERS = [
43
44
  {value: '2160p', label: '2160p (4k)'},
44
45
  ] as const satisfies {value: UploadConfig['max_resolution_tier']; label: string}[]
45
46
 
47
+ const ADVANCED_RESOLUTIONS: {value: StaticRenditionResolution; label: string}[] = [
48
+ {value: '270p', label: '270p'},
49
+ {value: '360p', label: '360p'},
50
+ {value: '480p', label: '480p'},
51
+ {value: '540p', label: '540p'},
52
+ {value: '720p', label: '720p'},
53
+ {value: '1080p', label: '1080p'},
54
+ {value: '1440p', label: '1440p'},
55
+ {value: '2160p', label: '2160p'},
56
+ ]
57
+
58
+ /**
59
+ * Sanitizes static renditions configuration to ensure 'highest' is not mixed with specific resolutions.
60
+ * If both are present, only 'highest' (and 'audio-only' if present) will be kept.
61
+ */
62
+ function sanitizeStaticRenditions(
63
+ renditions: StaticRenditionResolution[]
64
+ ): StaticRenditionResolution[] {
65
+ const hasHighest = renditions.includes('highest')
66
+ const hasSpecificResolutions = renditions.some((r) => r !== 'highest' && r !== 'audio-only')
67
+
68
+ if (hasHighest && hasSpecificResolutions) {
69
+ return renditions.filter((r) => r === 'highest' || r === 'audio-only')
70
+ }
71
+
72
+ return renditions
73
+ }
74
+
46
75
  /**
47
76
  * The modal for configuring a staged upload. Handles triggering of the asset
48
77
  * upload, even if no modal needs to be shown.
@@ -84,7 +113,7 @@ export default function UploadConfiguration({
84
113
  if (action.value === 'basic') {
85
114
  return Object.assign({}, prev, {
86
115
  video_quality: action.value,
87
- mp4_support: 'none',
116
+ static_renditions: [],
88
117
  max_resolution_tier: '1080p',
89
118
  text_tracks: prev.text_tracks?.filter(({type}) => type !== 'autogenerated'),
90
119
  public_policy: true,
@@ -94,12 +123,12 @@ export default function UploadConfiguration({
94
123
  }
95
124
  return Object.assign({}, prev, {
96
125
  video_quality: action.value,
97
- mp4_support: pluginConfig.mp4_support,
126
+ static_renditions: sanitizeStaticRenditions(pluginConfig.static_renditions || []),
98
127
  max_resolution_tier: pluginConfig.max_resolution_tier,
99
128
  text_tracks: [...autoTextTracks, ...(prev.text_tracks || [])],
100
129
  })
101
130
 
102
- case 'mp4_support':
131
+ case 'static_renditions':
103
132
  case 'max_resolution_tier':
104
133
  case 'normalize_audio':
105
134
  case 'signed_policy':
@@ -141,7 +170,7 @@ export default function UploadConfiguration({
141
170
  {
142
171
  video_quality: pluginConfig.video_quality,
143
172
  max_resolution_tier: pluginConfig.max_resolution_tier,
144
- mp4_support: pluginConfig.mp4_support,
173
+ static_renditions: sanitizeStaticRenditions(pluginConfig.static_renditions || []),
145
174
  signed_policy: secrets.enableSignedUrls && pluginConfig.defaultSigned,
146
175
  public_policy: pluginConfig.defaultPublic,
147
176
  normalize_audio: pluginConfig.normalize_audio,
@@ -149,6 +178,54 @@ export default function UploadConfiguration({
149
178
  } as UploadConfig
150
179
  )
151
180
 
181
+ // Determine if user is in advanced mode based on selected renditions
182
+ const isAdvancedMode = useMemo(() => {
183
+ const specificResolutions = config.static_renditions.filter(
184
+ (r) => r !== 'highest' && r !== 'audio-only'
185
+ )
186
+ return specificResolutions.length > 0
187
+ }, [config.static_renditions])
188
+
189
+ const [renditionMode, setRenditionMode] = useState<'standard' | 'advanced'>(
190
+ isAdvancedMode ? 'advanced' : 'standard'
191
+ )
192
+
193
+ // Helper to toggle a rendition
194
+ const toggleRendition = (rendition: StaticRenditionResolution) => {
195
+ const current = config.static_renditions
196
+ const hasRendition = current.includes(rendition)
197
+
198
+ if (hasRendition) {
199
+ dispatch({
200
+ action: 'static_renditions',
201
+ value: current.filter((r) => r !== rendition),
202
+ })
203
+ } else {
204
+ dispatch({
205
+ action: 'static_renditions',
206
+ value: [...current, rendition],
207
+ })
208
+ }
209
+ }
210
+
211
+ // When switching modes, clear renditions that don't apply
212
+ const handleModeChange = (mode: 'standard' | 'advanced') => {
213
+ setRenditionMode(mode)
214
+ if (mode === 'standard') {
215
+ // Remove specific resolutions, keep only highest and audio-only
216
+ dispatch({
217
+ action: 'static_renditions',
218
+ value: config.static_renditions.filter((r) => r === 'highest' || r === 'audio-only'),
219
+ })
220
+ } else {
221
+ // Remove highest, keep specific resolutions and audio-only
222
+ dispatch({
223
+ action: 'static_renditions',
224
+ value: config.static_renditions.filter((r) => r !== 'highest'),
225
+ })
226
+ }
227
+ }
228
+
152
229
  // If user-provided config is disabled, begin the upload immediately with
153
230
  // the developer-specified values from the schema or config or defaults.
154
231
  // This can include auto-generated subtitles!
@@ -290,31 +367,111 @@ export default function UploadConfiguration({
290
367
 
291
368
  {!basicConfig && (
292
369
  <FormField title="Additional Configuration">
293
- <Stack space={2}>
370
+ <Stack space={3}>
294
371
  <PlaybackPolicy id={id} config={config} secrets={secrets} dispatch={dispatch} />
295
372
 
296
- {!basicConfig && (
297
- <Flex align="center" gap={2} padding={[0, 2]}>
298
- <Checkbox
299
- id={`${id}--mp4_support`}
300
- style={{display: 'block'}}
301
- name="mp4_support"
302
- required
303
- checked={config.mp4_support === 'standard'}
304
- onChange={(e) =>
305
- dispatch({
306
- action: 'mp4_support',
307
- value: e.currentTarget.checked ? 'standard' : 'none',
308
- })
309
- }
310
- />
311
- <Text>
312
- <label htmlFor={`${id}--mp4_support`}>
313
- MP4 support (allow downloading)
314
- </label>
315
- </Text>
316
- </Flex>
317
- )}
373
+ <Stack space={3}>
374
+ <FormField
375
+ title="Static Renditions"
376
+ description="Generate downloadable MP4 or M4A files. Note: Mux will not upscale to produce MP4 renditions - renditions that would cause upscaling are skipped."
377
+ >
378
+ <Stack space={3}>
379
+ {/* Mode Selector */}
380
+ <Flex gap={3}>
381
+ <Flex align="center" gap={2}>
382
+ <Radio
383
+ checked={renditionMode === 'standard'}
384
+ name="rendition-mode"
385
+ onChange={() => handleModeChange('standard')}
386
+ value="standard"
387
+ id={`${id}--mode-standard`}
388
+ />
389
+ <Text as="label" htmlFor={`${id}--mode-standard`}>
390
+ Standard
391
+ </Text>
392
+ </Flex>
393
+ <Flex align="center" gap={2}>
394
+ <Radio
395
+ checked={renditionMode === 'advanced'}
396
+ name="rendition-mode"
397
+ onChange={() => handleModeChange('advanced')}
398
+ value="advanced"
399
+ id={`${id}--mode-advanced`}
400
+ />
401
+ <Text as="label" htmlFor={`${id}--mode-advanced`}>
402
+ Advanced
403
+ </Text>
404
+ </Flex>
405
+ </Flex>
406
+
407
+ {/* Standard Mode Options */}
408
+ {renditionMode === 'standard' && (
409
+ <Stack space={2}>
410
+ <Flex align="center" gap={2} padding={[0, 2]}>
411
+ <Checkbox
412
+ id={`${id}--highest`}
413
+ style={{display: 'block'}}
414
+ checked={config.static_renditions.includes('highest')}
415
+ onChange={() => toggleRendition('highest')}
416
+ />
417
+ <Text as="label" htmlFor={`${id}--highest`}>
418
+ Highest Resolution (up to 4K)
419
+ </Text>
420
+ </Flex>
421
+ <Flex align="center" gap={2} padding={[0, 2]}>
422
+ <Checkbox
423
+ id={`${id}--audio-only-standard`}
424
+ style={{display: 'block'}}
425
+ checked={config.static_renditions.includes('audio-only')}
426
+ onChange={() => toggleRendition('audio-only')}
427
+ />
428
+ <Text as="label" htmlFor={`${id}--audio-only-standard`}>
429
+ Audio Only (M4A)
430
+ </Text>
431
+ </Flex>
432
+ </Stack>
433
+ )}
434
+
435
+ {/* Advanced Mode Options */}
436
+ {renditionMode === 'advanced' && (
437
+ <Stack space={2}>
438
+ <Label size={1} muted>
439
+ Select specific resolutions:
440
+ </Label>
441
+ <Flex gap={2} wrap="wrap">
442
+ {ADVANCED_RESOLUTIONS.map(({value, label}) => {
443
+ const inputId = `${id}--resolution-${value}`
444
+ return (
445
+ <Flex key={value} align="center" gap={2}>
446
+ <Checkbox
447
+ id={inputId}
448
+ style={{display: 'block'}}
449
+ checked={config.static_renditions.includes(value)}
450
+ onChange={() => toggleRendition(value)}
451
+ />
452
+ <Text as="label" htmlFor={inputId} size={1}>
453
+ {label}
454
+ </Text>
455
+ </Flex>
456
+ )
457
+ })}
458
+ </Flex>
459
+ <Flex align="center" gap={2} padding={[2, 2, 0, 2]}>
460
+ <Checkbox
461
+ id={`${id}--audio-only-advanced`}
462
+ style={{display: 'block'}}
463
+ checked={config.static_renditions.includes('audio-only')}
464
+ onChange={() => toggleRendition('audio-only')}
465
+ />
466
+ <Text as="label" htmlFor={`${id}--audio-only-advanced`}>
467
+ Audio Only (M4A)
468
+ </Text>
469
+ </Flex>
470
+ </Stack>
471
+ )}
472
+ </Stack>
473
+ </FormField>
474
+ </Stack>
318
475
  </Stack>
319
476
  </FormField>
320
477
  )}
@@ -385,7 +542,10 @@ function formatUploadConfig(config: UploadConfig): MuxNewAssetSettings {
385
542
  [] as NonNullable<MuxNewAssetSettings['input']>
386
543
  ),
387
544
  ],
388
- mp4_support: config.mp4_support,
545
+ static_renditions:
546
+ config.static_renditions.length > 0
547
+ ? config.static_renditions.map((resolution) => ({resolution}))
548
+ : undefined,
389
549
  playback_policy: setPlaybackPolicy(config),
390
550
  max_resolution_tier: config.max_resolution_tier,
391
551
  video_quality: config.video_quality,
@@ -10,11 +10,27 @@ export const useMuxPolling = (asset?: VideoAssetDocument) => {
10
10
  const client = useClient()
11
11
  const projectId = useProjectId()
12
12
  const dataset = useDataset()
13
+ const isPreparingStaticRenditions = useMemo(() => {
14
+ // Legacy: If static_renditions has a status field, it was created with mp4_support (deprecated)
15
+ // We don't process this old format, just return false
16
+ // Note: 'disabled' status is valid in the new format when no renditions were requested
17
+ if (
18
+ asset?.data?.static_renditions?.status &&
19
+ asset?.data?.static_renditions?.status !== 'disabled'
20
+ ) {
21
+ return false
22
+ }
23
+
24
+ const files = asset?.data?.static_renditions?.files
25
+ if (!files || files.length === 0) {
26
+ return false
27
+ }
28
+ return files.some((file) => file.status === 'preparing')
29
+ }, [asset?.data?.static_renditions?.status, asset?.data?.static_renditions?.files])
30
+
13
31
  const shouldFetch = useMemo(
14
- () =>
15
- !!asset?.assetId &&
16
- (asset?.status === 'preparing' || asset?.data?.static_renditions?.status === 'preparing'),
17
- [asset?.assetId, asset?.data?.static_renditions?.status, asset?.status]
32
+ () => !!asset?.assetId && (asset?.status === 'preparing' || isPreparingStaticRenditions),
33
+ [asset?.assetId, asset?.status, isPreparingStaticRenditions]
18
34
  )
19
35
  return useSWR(
20
36
  shouldFetch ? `/${projectId}/addons/mux/assets/${dataset}/data/${asset?.assetId}` : null,
package/src/schema.ts CHANGED
@@ -39,12 +39,18 @@ const muxStaticRenditionFile = {
39
39
  name: 'mux.staticRenditionFile',
40
40
  type: 'object',
41
41
  fields: [
42
- {type: 'string', name: 'ext'},
43
42
  {type: 'string', name: 'name'},
43
+ {type: 'string', name: 'ext'},
44
+ {type: 'number', name: 'height'},
44
45
  {type: 'number', name: 'width'},
45
46
  {type: 'number', name: 'bitrate'},
46
- {type: 'number', name: 'filesize'},
47
- {type: 'number', name: 'height'},
47
+ {type: 'string', name: 'filesize'},
48
+ {type: 'string', name: 'type'},
49
+ {type: 'string', name: 'status'},
50
+ {type: 'string', name: 'resolution_tier'},
51
+ {type: 'string', name: 'resolution'},
52
+ {type: 'string', name: 'id'},
53
+ {type: 'string', name: 'passthrough'},
48
54
  ],
49
55
  }
50
56
 
package/src/util/types.ts CHANGED
@@ -1,8 +1,42 @@
1
1
  import type {ObjectInputProps, PreviewLayoutKey, PreviewProps, SchemaType} from 'sanity'
2
2
  import type {PartialDeep} from 'type-fest'
3
3
 
4
+ /**
5
+ * Standard static rendition options available for plugin configuration defaults
6
+ */
7
+ export type StandardRendition = 'highest' | 'audio-only'
8
+
9
+ /**
10
+ * All static rendition resolution options supported by Mux
11
+ */
12
+ export type StaticRenditionResolution =
13
+ | 'highest'
14
+ | 'audio-only'
15
+ | '270p'
16
+ | '360p'
17
+ | '480p'
18
+ | '540p'
19
+ | '720p'
20
+ | '1080p'
21
+ | '1440p'
22
+ | '2160p'
23
+
4
24
  export interface MuxInputConfig {
5
25
  /**
26
+ * Enable static renditions by default. Can be overwritten on a per-asset basis.
27
+ * Supports:
28
+ * - Standard mode: 'highest' (up to 4K MP4) and/or 'audio-only' (M4A)
29
+ * - Advanced mode: Specific resolutions ('270p', '360p', '480p', '540p', '720p', '1080p', '1440p', '2160p') and/or 'audio-only'
30
+ *
31
+ * **Important**: 'highest' cannot be mixed with specific resolutions. If both are provided, only 'highest' will be used.
32
+ *
33
+ * @see {@link https://docs.mux.com/guides/video/enable-static-mp4-renditions}
34
+ * @defaultValue []
35
+ */
36
+ static_renditions: StaticRenditionResolution[]
37
+
38
+ /**
39
+ * @deprecated Use `static_renditions` instead. This field is kept for backward compatibility.
6
40
  * Enable static renditions by setting this to 'standard'. Can be overwritten on a per-asset basis.
7
41
  * Requires `"video_quality": "plus"`
8
42
  * @see {@link https://docs.mux.com/guides/video/enable-static-mp4-renditions#why-enable-mp4-support}
@@ -182,10 +216,8 @@ export function isAutogeneratedTrack(
182
216
  export type UploadTextTrack = AutogeneratedTextTrack | CustomTextTrack
183
217
 
184
218
  export interface UploadConfig
185
- extends Pick<
186
- MuxInputConfig,
187
- 'max_resolution_tier' | 'mp4_support' | 'normalize_audio' | 'video_quality'
188
- > {
219
+ extends Pick<MuxInputConfig, 'max_resolution_tier' | 'normalize_audio' | 'video_quality'> {
220
+ static_renditions: StaticRenditionResolution[]
189
221
  text_tracks: UploadTextTrack[]
190
222
  signed_policy: boolean
191
223
  public_policy: boolean
@@ -196,10 +228,9 @@ export interface UploadConfig
196
228
  * @docs {@link https://docs.mux.com/api-reference#video/operation/create-direct-upload}
197
229
  */
198
230
  export interface MuxNewAssetSettings
199
- extends Pick<
200
- MuxInputConfig,
201
- 'max_resolution_tier' | 'mp4_support' | 'normalize_audio' | 'video_quality'
202
- > {
231
+ extends Pick<MuxInputConfig, 'max_resolution_tier' | 'normalize_audio' | 'video_quality'> {
232
+ /** Static renditions configuration for downloadable files */
233
+ static_renditions?: {resolution: StaticRenditionResolution}[]
203
234
  /** An array of objects that each describe an input file to be used to create the asset.*/
204
235
  input?: {
205
236
  /** The URL of the file that Mux should download and use. */
@@ -371,12 +402,18 @@ export interface MuxAsset {
371
402
  static_renditions?: {
372
403
  status: 'ready' | 'preparing' | 'disabled' | 'errored'
373
404
  files: {
374
- name: 'low.mp4' | 'medium.mp4' | 'high.mp4' | 'audio.m4a'
405
+ name: string
375
406
  ext: 'mp4' | 'm4a'
376
407
  height: number
377
408
  width: number
378
409
  bitrate: number
379
- filesize: number
410
+ filesize: string
411
+ type: 'standard' | 'advanced'
412
+ status: 'ready' | 'preparing' | 'skipped' | 'errored'
413
+ resolution_tier?: string
414
+ resolution?: string
415
+ id: string
416
+ passthrough?: string
380
417
  }[]
381
418
  }
382
419
  recording_times?: {