Handle conversion of too-big assets on web
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
|||||||
import {msg, plural} from '@lingui/macro'
|
import {msg, plural} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {VIDEO_MAX_DURATION_MS} from '#/lib/constants'
|
import {VIDEO_MAX_DURATION_MS, VIDEO_MAX_SIZE} from '#/lib/constants'
|
||||||
import {
|
import {
|
||||||
usePhotoLibraryPermission,
|
usePhotoLibraryPermission,
|
||||||
useVideoLibraryPermission,
|
useVideoLibraryPermission,
|
||||||
@@ -60,6 +60,7 @@ enum SelectedAssetError {
|
|||||||
MaxImages = 'MaxImages',
|
MaxImages = 'MaxImages',
|
||||||
MaxVideos = 'MaxVideos',
|
MaxVideos = 'MaxVideos',
|
||||||
VideoTooLong = 'VideoTooLong',
|
VideoTooLong = 'VideoTooLong',
|
||||||
|
FileTooBig = 'FileTooBig',
|
||||||
MaxGIFs = 'MaxGIFs',
|
MaxGIFs = 'MaxGIFs',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,12 +186,6 @@ function classifyImagePickerAsset(asset: ImagePickerAsset):
|
|||||||
type = 'image'
|
type = 'image'
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log({
|
|
||||||
asset,
|
|
||||||
type,
|
|
||||||
mimeType,
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we weren't able to find a valid type, we don't support this asset.
|
* If we weren't able to find a valid type, we don't support this asset.
|
||||||
*/
|
*/
|
||||||
@@ -209,22 +204,82 @@ function classifyImagePickerAsset(asset: ImagePickerAsset):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
function dataURItoBlob(uri: string, mimeType: string) {
|
||||||
* WEB ONLY. On web, certain file formats (like `.mov`) don't give us a
|
const [, data] = uri.split(',')
|
||||||
* duration or dimensions, so we need to load the file manually to extract
|
const binary = atob(data)
|
||||||
* this.
|
// Convert to array of bytes
|
||||||
*/
|
const array = new Uint8Array(binary.length)
|
||||||
async function getAdditionalVideoMetadata(asset: ValidatedImagePickerAsset) {
|
for (let i = 0; i < binary.length; i++) {
|
||||||
if (isNative) return asset
|
array[i] = binary.charCodeAt(i)
|
||||||
const file = await fetch(asset.uri)
|
}
|
||||||
.then(res => res.blob())
|
// Create and return the Blob
|
||||||
.then(
|
return new Blob([array], {type: mimeType})
|
||||||
blob =>
|
}
|
||||||
new File([blob], `tmp.${mimeToExt(asset.mimeType)}`, {
|
|
||||||
type: asset.mimeType,
|
export enum GetMetadataError {
|
||||||
}),
|
FileTooLarge = 'FileTooLarge',
|
||||||
)
|
UnknownExtension = 'UnknownExtension',
|
||||||
return await getVideoMetadata(file)
|
FileCreationFailure = 'FileCreationFailure',
|
||||||
|
MetadataExtractionFailure = 'MetadataExtractionFailure',
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMetadata(
|
||||||
|
uri: string,
|
||||||
|
mimeType?: string,
|
||||||
|
): Promise<
|
||||||
|
| {
|
||||||
|
error: GetMetadataError
|
||||||
|
asset: undefined
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
error: undefined
|
||||||
|
asset: ImagePickerAsset
|
||||||
|
}
|
||||||
|
> {
|
||||||
|
const mime = mimeType || extractDataUriMime(uri)
|
||||||
|
const blob = dataURItoBlob(uri, mime)
|
||||||
|
|
||||||
|
if (blob.size > VIDEO_MAX_SIZE) {
|
||||||
|
return {
|
||||||
|
error: GetMetadataError.FileTooLarge,
|
||||||
|
asset: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = mimeToExt(mime)
|
||||||
|
|
||||||
|
if (!ext) {
|
||||||
|
return {
|
||||||
|
error: GetMetadataError.UnknownExtension,
|
||||||
|
asset: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = new File([blob], `tmp.${ext}`, {
|
||||||
|
type: mime,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
return {
|
||||||
|
error: GetMetadataError.FileCreationFailure,
|
||||||
|
asset: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const asset = await getVideoMetadata(file)
|
||||||
|
return {
|
||||||
|
error: undefined,
|
||||||
|
asset,
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`getMetadata: failed to get file metadata`, {
|
||||||
|
safeMessage: e instanceof Error ? e.message : String(e),
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
error: GetMetadataError.MetadataExtractionFailure,
|
||||||
|
asset: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -322,17 +377,30 @@ async function processImagePickerAssets(
|
|||||||
const selectedVideo = supportedAssets[0]
|
const selectedVideo = supportedAssets[0]
|
||||||
|
|
||||||
if (typeof selectedVideo.duration !== 'number') {
|
if (typeof selectedVideo.duration !== 'number') {
|
||||||
try {
|
/*
|
||||||
const metadata = await getAdditionalVideoMetadata(selectedVideo)
|
* We can only do this on web
|
||||||
selectedVideo.duration = metadata.duration
|
*/
|
||||||
selectedVideo.width = metadata.width
|
if (isWeb) {
|
||||||
selectedVideo.height = metadata.height
|
const {error, asset} = await getMetadata(
|
||||||
} catch (e: any) {
|
selectedVideo.uri,
|
||||||
logger.error(`processSelectedAssets: failed to get video metadata`, {
|
selectedVideo.mimeType,
|
||||||
safeMessage: e.message,
|
)
|
||||||
})
|
if (error) {
|
||||||
errors.add(SelectedAssetError.Unsupported)
|
switch (error) {
|
||||||
supportedAssets = []
|
case GetMetadataError.FileTooLarge:
|
||||||
|
errors.add(SelectedAssetError.FileTooBig)
|
||||||
|
supportedAssets = []
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
errors.add(SelectedAssetError.Unsupported)
|
||||||
|
supportedAssets = []
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedVideo.duration = asset.duration
|
||||||
|
selectedVideo.width = asset.width
|
||||||
|
selectedVideo.height = asset.height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
@@ -382,6 +450,12 @@ export function SelectMediaButton({
|
|||||||
|
|
||||||
const processSelectedAssets = useCallback(
|
const processSelectedAssets = useCallback(
|
||||||
async (rawAssets: ImagePickerAsset[]) => {
|
async (rawAssets: ImagePickerAsset[]) => {
|
||||||
|
// const {uri, ...rest} = rawAssets[0] || {}
|
||||||
|
// alert(JSON.stringify({
|
||||||
|
// uri: uri.slice(0, 40),
|
||||||
|
// ...rest
|
||||||
|
// }, null, 2))
|
||||||
|
// return
|
||||||
const {
|
const {
|
||||||
type,
|
type,
|
||||||
assets,
|
assets,
|
||||||
@@ -419,6 +493,9 @@ export function SelectMediaButton({
|
|||||||
[SelectedAssetError.MaxGIFs]: _(
|
[SelectedAssetError.MaxGIFs]: _(
|
||||||
msg`You can only select one GIF at a time.`,
|
msg`You can only select one GIF at a time.`,
|
||||||
),
|
),
|
||||||
|
[SelectedAssetError.FileTooBig]: _(
|
||||||
|
msg`This file is too large. Maximum size is 100mb.`,
|
||||||
|
),
|
||||||
}[error]
|
}[error]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -470,6 +547,12 @@ export function SelectMediaButton({
|
|||||||
if (canceled) return
|
if (canceled) return
|
||||||
|
|
||||||
await processSelectedAssets(assets)
|
await processSelectedAssets(assets)
|
||||||
|
// if (isNative) {
|
||||||
|
// } else if (isWeb) {
|
||||||
|
// const {assets, canceled} = await pickVideo()
|
||||||
|
|
||||||
|
// await processSelectedAssets(assets)
|
||||||
|
// }
|
||||||
}, [
|
}, [
|
||||||
_,
|
_,
|
||||||
requestPhotoAccessIfNeeded,
|
requestPhotoAccessIfNeeded,
|
||||||
|
|||||||
Reference in New Issue
Block a user