Spaces:
Running
Running
| import { | |
| generateVideoThumbnails, | |
| getVideoDurationFromVideoFile, | |
| } from '@rajesh896/video-thumbnails-generator'; | |
| import { useCallback, useState } from 'react'; | |
| import { DropzoneOptions, useDropzone } from 'react-dropzone'; | |
| import { toast } from 'react-hot-toast'; | |
| import { fetcher } from '../utils'; | |
| import { SignedPayload } from '../types'; | |
| const useMediaUpload = ( | |
| onUpload: (uploadUrl: string) => void, | |
| options?: Partial<DropzoneOptions>, | |
| ) => { | |
| const [isUploading, setUploading] = useState(false); | |
| const upload = useCallback(async (file: File, chatId?: string) => { | |
| const { id, signedUrl, publicUrl, fields } = await fetcher<SignedPayload>( | |
| '/api/sign', | |
| { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| id: chatId, | |
| fileType: file.type, | |
| fileName: file.name, | |
| }), | |
| }, | |
| ); | |
| const formData = new FormData(); | |
| Object.entries(fields).forEach(([key, value]) => { | |
| formData.append(key, value as string); | |
| }); | |
| formData.append('file', file); | |
| const uploadResponse = await fetch(signedUrl, { | |
| method: 'POST', | |
| body: formData, | |
| }); | |
| if (!uploadResponse.ok) { | |
| toast.error(uploadResponse.statusText); | |
| return; | |
| } | |
| return { | |
| id, | |
| publicUrl, | |
| }; | |
| }, []); | |
| const { getRootProps, getInputProps, isDragActive, open } = useDropzone({ | |
| accept: { | |
| 'image/*': ['.jpeg', '.png'], | |
| 'video/mp4': ['.mp4', '.MP4'], | |
| }, | |
| noClick: true, | |
| noKeyboard: true, | |
| multiple: false, | |
| onDrop: async files => { | |
| if (files.length !== 1) { | |
| throw new Error('Only one image can be uploaded at a time'); | |
| } | |
| setUploading(true); | |
| const reader = new FileReader(); | |
| reader.readAsDataURL(files[0]); | |
| reader.onload = async () => { | |
| const file = files[0]; | |
| if (file.type === 'video/mp4') { | |
| const duration = await getVideoDurationFromVideoFile(file); | |
| if (duration > 30) { | |
| setUploading(false); | |
| toast.error('Video duration must be less than 30 seconds'); | |
| return; | |
| } | |
| } else if (file.size > 2.5 * 1024 * 1024) { | |
| setUploading(false); | |
| toast.error('Image size must be less than 2.5MB'); | |
| return; | |
| } | |
| const resp = await upload(file); | |
| if (!resp) { | |
| return; | |
| } | |
| if (file.type === 'video/mp4') { | |
| const thumbnails = await generateVideoThumbnails(file, 1, 'file'); | |
| fetch(thumbnails[0]) | |
| .then(res => res.blob()) | |
| .then(blob => { | |
| const thumbnailFile = new File( | |
| [blob], | |
| file.name.replace('.mp4', '.png').replace('.MP4', '.png'), | |
| { | |
| type: 'image/png', | |
| }, | |
| ); | |
| return upload(thumbnailFile, resp.id); | |
| }); | |
| } | |
| onUpload(resp.publicUrl); | |
| setUploading(false); | |
| }; | |
| }, | |
| ...options, | |
| }); | |
| return { | |
| getRootProps, | |
| getInputProps, | |
| isDragActive, | |
| isUploading, | |
| openUpload: open, | |
| }; | |
| }; | |
| export default useMediaUpload; | |