From e33e8550f6d4340cd4e0b56b73bad2d24e2262e4 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Thu, 9 Jun 2022 22:57:54 +0200 Subject: [PATCH] Prepare for future media needs NOT USED --- src/components/mediaTransformation.ts | 149 ++++++++++++++++++++++++++ src/utils/slices/instancesSlice.ts | 37 +++++++ 2 files changed, 186 insertions(+) create mode 100644 src/components/mediaTransformation.ts diff --git a/src/components/mediaTransformation.ts b/src/components/mediaTransformation.ts new file mode 100644 index 00000000..29bb542d --- /dev/null +++ b/src/components/mediaTransformation.ts @@ -0,0 +1,149 @@ +import { store } from '@root/store' +import { getInstanceConfigurationMediaAttachments } from '@utils/slices/instancesSlice' +import { Action, manipulateAsync, SaveFormat } from 'expo-image-manipulator' +import i18next from 'i18next' +import { Platform } from 'react-native' +import ImagePicker from 'react-native-image-crop-picker' + +export interface Props { + type: 'image' | 'video' + uri: string // This can be pure path or uri starting with file:// + mime?: string + transform: { + imageFormat?: SaveFormat.JPEG | SaveFormat.PNG + resize?: boolean + width?: number + height?: number + } +} + +const getFileExtension = (uri: string) => { + const extension = uri.split('.').pop() + // Using mime type standard of jpeg + return extension === 'jpg' ? 'jpeg' : extension +} + +const mediaTransformation = async ({ + type, + uri, + mime, + transform +}: Props): Promise<{ + uri: string + mime: string + width: number + height: number +}> => { + const configurationMediaAttachments = + getInstanceConfigurationMediaAttachments(store.getState()) + + const fileExtension = getFileExtension(uri) + + switch (type) { + case 'image': + if (mime === 'image/gif' || fileExtension === 'gif') { + return Promise.reject('GIFs should not be transformed') + } + let targetFormat: SaveFormat.JPEG | SaveFormat.PNG = SaveFormat.JPEG + + const supportedImageTypes = + configurationMediaAttachments.supported_mime_types.filter(mime => + mime.startsWith('image/') + ) + + // @ts-ignore + const transformations: Action[] = [ + !transform.resize && (transform.width || transform.height) + ? { + resize: { width: transform.width, height: transform.height } + } + : null + ].filter(t => !!t) + + if (mime) { + if ( + mime !== `image/${fileExtension}` || + !supportedImageTypes.includes(mime) + ) { + targetFormat = transform.imageFormat || SaveFormat.JPEG + } else { + targetFormat = mime.split('/').pop() as any + } + } else { + if (!fileExtension) { + return Promise.reject('Unable to get file extension') + } + if (!supportedImageTypes.includes(`image/${fileExtension}`)) { + targetFormat = transform.imageFormat || SaveFormat.JPEG + } else { + targetFormat = fileExtension as any + } + } + + const converted = await manipulateAsync(uri, transformations, { + base64: false, + compress: Platform.OS === 'ios' ? 0.8 : 1, + format: targetFormat + }) + + if (transform.resize) { + const resized = await ImagePicker.openCropper({ + mediaType: 'photo', + path: converted.uri, + width: transform.width, + height: transform.height, + cropperChooseText: i18next.t('common:buttons.apply'), + cropperCancelText: i18next.t('common:buttons.cancel'), + hideBottomControls: true + }) + if (!resized) { + return Promise.reject('Resize failed') + } else { + return { + uri: resized.path, + mime: resized.mime, + width: resized.width, + height: resized.height + } + } + } else { + return { + uri: converted.uri, + mime: transform.imageFormat || SaveFormat.JPEG, + width: converted.width, + height: converted.height + } + } + case 'video': + const supportedVideoTypes = + configurationMediaAttachments.supported_mime_types.filter(mime => + mime.startsWith('video/') + ) + + if (mime) { + if (mime !== `video/${fileExtension}`) { + console.warn('Video mime type and file extension does not match') + } + if (!supportedVideoTypes.includes(mime)) { + return Promise.reject('Video file type is not supported') + } + } else { + if (!fileExtension) { + return Promise.reject('Unable to get file extension') + } + if (!supportedVideoTypes.includes(`video/${fileExtension}`)) { + return Promise.reject('Video file type is not supported') + } + } + + return { + uri: uri, + mime: mime || `video/${fileExtension}`, + width: 0, + height: 0 + } + break + } +} + +export default mediaTransformation diff --git a/src/utils/slices/instancesSlice.ts b/src/utils/slices/instancesSlice.ts index fd9d845e..d6693c7d 100644 --- a/src/utils/slices/instancesSlice.ts +++ b/src/utils/slices/instancesSlice.ts @@ -381,6 +381,43 @@ export const getInstanceConfigurationStatusCharsURL = ({ instances[findInstanceActive(instances)]?.configuration?.statuses .characters_reserved_per_url || 23 +export const getInstanceConfigurationMediaAttachments = ({ + instances: { instances } +}: RootState) => + instances[findInstanceActive(instances)]?.configuration + ?.media_attachments || { + supported_mime_types: [ + 'image/jpeg', + 'image/png', + 'image/gif', + 'video/webm', + 'video/mp4', + 'video/quicktime', + 'video/ogg', + 'audio/wave', + 'audio/wav', + 'audio/x-wav', + 'audio/x-pn-wave', + 'audio/ogg', + 'audio/vorbis', + 'audio/mpeg', + 'audio/mp3', + 'audio/webm', + 'audio/flac', + 'audio/aac', + 'audio/m4a', + 'audio/x-m4a', + 'audio/mp4', + 'audio/3gpp', + 'video/x-ms-asf' + ], + image_size_limit: 10485760, + image_matrix_limit: 16777216, + video_size_limit: 41943040, + video_frame_rate_limit: 60, + video_matrix_limit: 2304000 + } + export const getInstanceConfigurationPoll = ({ instances: { instances } }: RootState) =>