Use toolbox video compressor (#11246)
This commit is contained in:
@@ -112,6 +112,7 @@
|
||||
"@bsky.app/sift": "^0.3.9",
|
||||
"@bsky.app/tapper": "^0.6.1",
|
||||
"@bsky.app/video": "0.3.6",
|
||||
"@bsky.app/video-compressor": "0.2.0",
|
||||
"@emoji-mart/data": "^1.2.1",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
"@expo/html-elements": "^0.12.5",
|
||||
|
||||
Generated
+17
@@ -289,6 +289,9 @@ importers:
|
||||
'@bsky.app/video':
|
||||
specifier: 0.3.6
|
||||
version: 0.3.6(expo@54.0.35(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
'@bsky.app/video-compressor':
|
||||
specifier: 0.2.0
|
||||
version: 0.2.0(expo@54.0.35(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
'@emoji-mart/data':
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
@@ -1698,6 +1701,13 @@ packages:
|
||||
react-native: '*'
|
||||
tlds: '*'
|
||||
|
||||
'@bsky.app/video-compressor@0.2.0':
|
||||
resolution: {integrity: sha512-hQPT/zxy3MoODuN7QM5Q0jA5anrrwKfka4o9BTz821sAaiRWW8E4vdw4xsbuhfUXRNOSXknQbDHch2Toh178Xw==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
'@bsky.app/video@0.3.6':
|
||||
resolution: {integrity: sha512-Ny7vz9v1lQOw5Llw4xejtJo/Dp/lmdRw1XdUJucOXlq6puXO93tv3mNXUIIyOEVWEwDPg8j/kSQxG/mGy+/hdQ==}
|
||||
peerDependencies:
|
||||
@@ -10439,6 +10449,13 @@ snapshots:
|
||||
react-native: 0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
||||
tlds: 1.261.0
|
||||
|
||||
'@bsky.app/video-compressor@0.2.0(expo@54.0.35(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
expo: 54.0.35(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
mediabunny: 1.49.0
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
'@bsky.app/video@0.3.6(expo@54.0.35(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
expo: 54.0.35(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0)(@react-native/metro-config@0.86.0(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
@@ -1388,7 +1388,7 @@ export type Events = {
|
||||
// === Video upload funnel (Frontend Spec section D) ===
|
||||
// Every event carries uploadId (client-generated UUID, ties one upload
|
||||
// session end-to-end) + engine (compression engine id, e.g.
|
||||
// native:react-native-compressor@1.13.0). jobId is added once the server
|
||||
// native:@bsky.app/video-compressor@0.2.0). jobId is added once the server
|
||||
// returns it. Sizes / codecs / dimensions / timings only - never content.
|
||||
'video:upload:picked': {
|
||||
uploadId: string
|
||||
@@ -1407,7 +1407,7 @@ export type Events = {
|
||||
// Native-only. Raw container metadata returned by the new module's probe()
|
||||
// (bitrate, codec, HDR, frame rate, rotation, etc.). Fires once per upload
|
||||
// between compressStarted and the compressSkipped/compressCompleted decision.
|
||||
// The web (mediabunny) and legacy rn-compressor engines do not surface this.
|
||||
// The web mediabunny engine also emits this from its own probe.
|
||||
'video:upload:probed': {
|
||||
uploadId: string
|
||||
engine: string
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {getVideoMetaData, Video} from 'react-native-compressor'
|
||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||
import {compress, probe, type VideoMetadata} from '@bsky.app/video-compressor'
|
||||
|
||||
import {SUPPORTED_MIME_TYPES, type SupportedMimeTypes} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {probe} from '../../../../modules/expo-bluesky-video-compress'
|
||||
import {
|
||||
COMPRESSION_MAX_DIMENSION,
|
||||
COMPRESSION_TARGET_BITRATE,
|
||||
} from './constants'
|
||||
import {type CompressedVideo, type ProbedMetadata} from './types'
|
||||
import {extToMime} from './util'
|
||||
|
||||
const MIN_SIZE_FOR_COMPRESSION_BYTES = 25 * 1024 * 1024 // 25mb
|
||||
|
||||
@@ -23,7 +25,7 @@ export async function compressVideo(
|
||||
// future smart-skip thresholds. Failures must not block the upload.
|
||||
if (onProbe && file.mimeType !== 'image/gif') {
|
||||
try {
|
||||
onProbe(await probe(file.uri))
|
||||
onProbe(toProbedMetadata(await probe(file.uri)))
|
||||
} catch (e) {
|
||||
logger.debug('video probe failed', {safeMessage: e})
|
||||
}
|
||||
@@ -60,31 +62,27 @@ export async function compressVideo(
|
||||
}
|
||||
}
|
||||
|
||||
const compressed = await Video.compress(
|
||||
return compress(
|
||||
file.uri,
|
||||
{
|
||||
compressionMethod: 'manual',
|
||||
bitrate: 3_000_000, // 3mbps
|
||||
maxSize: 1920,
|
||||
targetBitrate: COMPRESSION_TARGET_BITRATE,
|
||||
maxSize: COMPRESSION_MAX_DIMENSION,
|
||||
codec: 'auto',
|
||||
frameRateCap: 30,
|
||||
mimeType: file.mimeType,
|
||||
fileSize: file.fileSize,
|
||||
// Force a transcode for unacceptable-format files regardless of size.
|
||||
// rnc's default minimumFileSizeForCompress would otherwise pass small
|
||||
// The compressor's default threshold would otherwise pass small
|
||||
// unacceptable-format files through unchanged and the server would
|
||||
// reject them. Acceptable formats are already short-circuited above so
|
||||
// they never reach this call.
|
||||
// WARNING: this ONE SPECIFIC ARG is in MB -sfn
|
||||
minimumFileSizeForCompress: 0,
|
||||
getCancellationId: id => {
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', () => {
|
||||
Video.cancelCompression(id)
|
||||
})
|
||||
}
|
||||
},
|
||||
passthroughBelowBytes: 0,
|
||||
passthroughGif: false,
|
||||
},
|
||||
onProgress,
|
||||
{onProgress, signal},
|
||||
)
|
||||
|
||||
const info = await getVideoMetaData(compressed)
|
||||
|
||||
return {uri: compressed, size: info.size, mimeType: extToMime(info.extension)}
|
||||
}
|
||||
|
||||
function toProbedMetadata(metadata: VideoMetadata): ProbedMetadata {
|
||||
return metadata
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
export const COMPRESSION_TARGET_BITRATE = 3_000_000 // 3 Mbps
|
||||
// Output dimension cap when compressing, and skip threshold for source files.
|
||||
export const COMPRESSION_MAX_DIMENSION = 1920
|
||||
// Web only: files under this size skip compression entirely. Native applies
|
||||
// its own threshold logic inside react-native-compressor.
|
||||
// Web only: files under this size skip compression entirely. Native uses a
|
||||
// 25 MiB threshold in compress.ts for compatibility with the previous engine.
|
||||
export const COMPRESSION_MIN_SIZE_BYTES = 25_000_000
|
||||
|
||||
@@ -18,7 +18,7 @@ type MetricFn = <E extends keyof Metrics>(event: E, payload: Metrics[E]) => void
|
||||
const COMPRESS_ENGINE =
|
||||
Platform.OS === 'web'
|
||||
? 'web:mediabunny@1.25.3'
|
||||
: 'native:react-native-compressor@1.13.0'
|
||||
: 'native:@bsky.app/video-compressor@0.2.0'
|
||||
|
||||
type Phase = 'compress' | 'upload' | 'processing'
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export type CompressedVideo = {
|
||||
}
|
||||
|
||||
// Source container metadata read off the input before any encoding decision.
|
||||
// Same shape across native (expo-bluesky-video-compress probe) and web
|
||||
// Same shape across native (@bsky.app/video-compressor probe) and web
|
||||
// (mediabunny Input + track inspection). Numbers are raw - no bucketing.
|
||||
export type ProbedMetadata = {
|
||||
mimeType: string
|
||||
|
||||
Reference in New Issue
Block a user