import pLimit from 'p-limit'; import { Alert } from 'react-native'; import { transformData } from '@/components/utils/objectFlat'; import { ExtendedAsset } from './types'; import { getMediaByDateRange } from './media'; import { checkMediaLibraryPermission, getFileExtension, getMimeType } from './utils'; import { convertHeicToJpeg, uploadVideoThumbnail } from './fileProcessor'; import { compressImage } from '../image-process/imageCompress'; import { getUploadUrl, confirmUpload, addMaterial } from './api'; import { uploadFile } from './uploader'; import * as MediaLibrary from 'expo-media-library'; // 设置最大并发数 const CONCURRENCY_LIMIT = 10; // 同时最多上传10个文件 const limit = pLimit(CONCURRENCY_LIMIT); // 处理单个媒体文件上传 export const processMediaUpload = async (asset: ExtendedAsset) => { try { // 检查权限 const { hasPermission } = await checkMediaLibraryPermission(); if (!hasPermission) { throw new Error('No media library permission'); } const isVideo = asset.mediaType === 'video'; // 上传原始文件 const uploadOriginalFile = async () => { try { let fileToUpload: File; const isVideo = asset.mediaType === 'video'; const mimeType = getMimeType(asset.filename || '', isVideo); // 生成文件名,保留原始扩展名 let filename = asset.filename || `${isVideo ? 'video' : 'image'}_${Date.now()}_original.${isVideo ? (getFileExtension(asset.filename || 'mp4') || 'mp4') : 'jpg'}`; // 处理 HEIC 格式 if (filename.toLowerCase().endsWith('.heic') || filename.toLowerCase().endsWith('.heif')) { fileToUpload = await convertHeicToJpeg(asset.uri); filename = filename.replace(/\.(heic|heif)$/i, '.jpg'); } else { // 获取资源信息 const assetInfo = await MediaLibrary.getAssetInfoAsync(asset.id, { shouldDownloadFromNetwork: true }); if (!assetInfo.localUri) { throw new Error('无法获取资源的本地路径'); } // 获取文件扩展名 const fileExtension = getFileExtension(assetInfo.filename || '') || (isVideo ? 'mp4' : 'jpg'); // 确保文件名有正确的扩展名 if (!filename.toLowerCase().endsWith(`.${fileExtension}`)) { const baseName = filename.split('.')[0]; filename = `${baseName}.${fileExtension}`; } // 获取文件内容 const response = await fetch(assetInfo.localUri); const blob = await response.blob(); // 创建文件对象 fileToUpload = new File([blob], filename, { type: mimeType }); console.log('文件准备上传:', { name: fileToUpload.name, type: fileToUpload.type, size: fileToUpload.size }); } // 准备元数据 let exifData = {}; if (asset.exif) { try { exifData = transformData({ ...asset, exif: { ...asset.exif, '{MakerApple}': undefined } }); } catch (exifError) { console.warn('处理 EXIF 数据时出错:', exifError); } } // 获取上传 URL const { upload_url, file_id } = await getUploadUrl(fileToUpload, { originalUri: asset.uri, creationTime: asset.creationTime, mediaType: isVideo ? 'video' : 'image', isCompressed: false, ...exifData, GPSVersionID: undefined }); // 上传文件 await uploadFile(fileToUpload, upload_url); await confirmUpload(file_id); console.log('文件上传成功:', { fileId: file_id, filename: fileToUpload.name, type: fileToUpload.type }); return { success: true, file_id, filename: fileToUpload.name }; } catch (error: any) { const errorMessage = error instanceof Error ? error.message : '未知错误'; console.error('上传原始文件时出错:', { error: errorMessage, assetId: asset.id, filename: asset.filename, uri: asset.uri }); throw new Error(`上传失败: ${errorMessage}`); } }; // 上传压缩文件(仅图片) const uploadCompressedFile = async () => { if (isVideo) return { success: true, file_id: null }; // 视频不压缩 try { const manipResult = await compressImage(asset.uri); const response = await fetch(manipResult.uri); const blob = await response.blob(); const filename = asset.filename ? `compressed_${asset.filename}` : `image_${Date.now()}_compressed.jpg`; const compressedFile = new File([blob], filename, { type: 'image/jpeg' }); const { upload_url, file_id } = await getUploadUrl(compressedFile, { originalUri: asset.uri, creationTime: asset.creationTime, mediaType: 'image', isCompressed: true }); await uploadFile(compressedFile, upload_url); await confirmUpload(file_id); return { success: true, file_id }; } catch (error) { return { success: false, error }; } }; // 先上传原始文件 const originalResult = await uploadOriginalFile(); // 如果是图片,再上传压缩文件 let compressedResult: { success: boolean; file_id?: string | null; error?: any } = { success: true, file_id: null }; if (!isVideo) { compressedResult = await uploadCompressedFile(); // 添加素材 addMaterial(originalResult.file_id, compressedResult?.file_id || ''); } else { // 上传压缩首帧 const thumbnailResult = await uploadVideoThumbnail(asset); if (thumbnailResult.success) { addMaterial(originalResult.file_id, thumbnailResult.file_id || ''); } } return { originalSuccess: originalResult.success, compressedSuccess: compressedResult.success, fileIds: { original: originalResult.file_id, compressed: compressedResult.file_id } }; } catch (error: any) { if (error.message === 'No media library permission') { throw error; } return { originalSuccess: false, compressedSuccess: false, error: error.message }; } }; // 手动触发上传 export const triggerManualUpload = async (startDate: Date, endDate: Date) => { try { const media = await getMediaByDateRange(startDate, endDate); if (media.length === 0) { Alert.alert('提示', '在指定时间范围内未找到媒体文件'); return []; } // 分离图片和视频 const photos = media.filter(item => item.mediaType === 'photo'); const videos = media.filter(item => item.mediaType === 'video'); console.log('videos11111111', videos); const results: any[] = []; // 处理所有图片(带并发控制) const processPhoto = async (item: any) => { try { const result = await processMediaUpload(item); results.push({ id: item.id, ...result }); } catch (error: any) { results.push({ id: item.id, originalSuccess: false, compressedSuccess: false, error: error.message }); } }; // 处理所有视频(带并发控制) const processVideo = async (item: any) => { try { const result = await processMediaUpload(item); results.push({ id: item.id, ...result }); } catch (error: any) { results.push({ id: item.id, originalSuccess: false, compressedSuccess: false, error: error.message }); } }; // 并发处理图片和视频 await Promise.all([ ...photos.map(photo => limit(() => processPhoto(photo))), ...videos.map(video => limit(() => processVideo(video))) ]); return results; } catch (error) { Alert.alert('错误', '上传过程中出现错误'); throw error; } }; export { registerBackgroundUploadTask } from './task';