feat: 图片exif信息处理
This commit is contained in:
parent
5072157a01
commit
036a7c7fa1
7
app.json
7
app.json
@ -12,6 +12,7 @@
|
||||
"supportsTablet": true,
|
||||
"infoPlist": {
|
||||
"NSPhotoLibraryUsageDescription": "Allow $(PRODUCT_NAME) to access your photos.",
|
||||
"NSPhotoLibraryAddUsageDescription": "需要保存图片到相册",
|
||||
"NSLocationWhenInUseUsageDescription": "Allow $(PRODUCT_NAME) to access your location to get photo location data.",
|
||||
"ITSAppUsesNonExemptEncryption": false,
|
||||
"UIBackgroundModes": ["fetch", "location", "audio"]
|
||||
@ -33,7 +34,9 @@
|
||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
||||
"android.permission.ACCESS_MEDIA_LOCATION",
|
||||
"FOREGROUND_SERVICE",
|
||||
"WAKE_LOCK"
|
||||
"WAKE_LOCK",
|
||||
"READ_EXTERNAL_STORAGE",
|
||||
"WRITE_EXTERNAL_STORAGE"
|
||||
],
|
||||
"edgeToEdgeEnabled": true,
|
||||
"package": "com.memowake.app"
|
||||
@ -48,7 +51,7 @@
|
||||
"expo-secure-store", [
|
||||
"expo-background-fetch",
|
||||
{
|
||||
"minimumInterval": 1
|
||||
"minimumInterval": 15
|
||||
}
|
||||
],
|
||||
[
|
||||
|
||||
@ -1,14 +1,44 @@
|
||||
import IP from '@/assets/icons/svg/ip.svg';
|
||||
import { registerBackgroundUploadTask, triggerManualUpload } from '@/components/file-upload/backgroundUploader';
|
||||
import Lottie from '@/components/lottie/lottie';
|
||||
import MessagePush from '@/components/message-push';
|
||||
import * as MediaLibrary from 'expo-media-library';
|
||||
import { useRouter } from 'expo-router';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Platform, Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
export default function HomeScreen() {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
let token;
|
||||
// 在组件中使用
|
||||
useEffect(() => {
|
||||
const setupBackgroundTask = async () => {
|
||||
try {
|
||||
// 请求必要的权限
|
||||
const { status } = await MediaLibrary.requestPermissionsAsync();
|
||||
if (status !== 'granted') {
|
||||
console.warn('Media library permission not granted');
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册后台任务
|
||||
await registerBackgroundUploadTask();
|
||||
|
||||
// 立即执行一次上传
|
||||
const now = new Date();
|
||||
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
triggerManualUpload(oneDayAgo, now);
|
||||
} catch (error) {
|
||||
console.error('Error setting up background task:', error);
|
||||
}
|
||||
};
|
||||
|
||||
setupBackgroundTask();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-bgPrimary px-[1rem] h-screen overflow-auto py-[2rem] pt-[10rem]">
|
||||
|
||||
@ -1,57 +1,130 @@
|
||||
import { fetchApi } from '@/lib/server-api-util';
|
||||
import * as BackgroundFetch from 'expo-background-fetch';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as ImageManipulator from 'expo-image-manipulator';
|
||||
import * as MediaLibrary from 'expo-media-library';
|
||||
import * as TaskManager from 'expo-task-manager';
|
||||
import pLimit from 'p-limit';
|
||||
import { Alert } from 'react-native';
|
||||
import { transformData } from '../utils/objectFlat';
|
||||
|
||||
type ExtendedAsset = MediaLibrary.Asset & {
|
||||
exif?: Record<string, any>;
|
||||
};
|
||||
|
||||
const BACKGROUND_UPLOAD_TASK = 'background-upload-task';
|
||||
// 设置最大并发数
|
||||
const CONCURRENCY_LIMIT = 10; // 同时最多上传10个文件
|
||||
|
||||
// 将 HEIC 图片转化
|
||||
const convertHeicToJpeg = async (uri: string): Promise<File> => {
|
||||
try {
|
||||
console.log('Starting HEIC to JPEG conversion for:', uri);
|
||||
|
||||
// 1. 将文件复制到缓存目录
|
||||
const cacheDir = FileSystem.cacheDirectory;
|
||||
if (!cacheDir) {
|
||||
throw new Error('Cache directory not available');
|
||||
}
|
||||
|
||||
// 创建唯一的文件名
|
||||
const tempUri = `${cacheDir}${Date.now()}.heic`;
|
||||
|
||||
// 复制文件到缓存目录
|
||||
await FileSystem.copyAsync({
|
||||
from: uri,
|
||||
to: tempUri
|
||||
});
|
||||
|
||||
// 2. 检查文件是否存在
|
||||
const fileInfo = await FileSystem.getInfoAsync(tempUri);
|
||||
if (!fileInfo.exists) {
|
||||
throw new Error('Temporary file was not created');
|
||||
}
|
||||
|
||||
// 3. 读取文件为 base64
|
||||
const base64 = await FileSystem.readAsStringAsync(tempUri, {
|
||||
encoding: FileSystem.EncodingType.Base64,
|
||||
});
|
||||
|
||||
if (!base64) {
|
||||
throw new Error('Failed to read file as base64');
|
||||
}
|
||||
|
||||
// 4. 创建 Blob
|
||||
const response = await fetch(`data:image/jpeg;base64,${base64}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Fetch failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
if (!blob || blob.size === 0) {
|
||||
throw new Error('Failed to create blob from base64');
|
||||
}
|
||||
|
||||
// 5. 创建文件名
|
||||
const originalName = uri.split('/').pop() || 'converted';
|
||||
const filename = originalName.replace(/\.(heic|heif)$/i, '.jpg');
|
||||
|
||||
console.log('Successfully converted HEIC to JPEG:', filename);
|
||||
|
||||
// 清理临时文件
|
||||
try {
|
||||
await FileSystem.deleteAsync(tempUri, { idempotent: true });
|
||||
} catch (cleanupError) {
|
||||
console.warn('Failed to clean up temporary file:', cleanupError);
|
||||
}
|
||||
|
||||
return new File([blob], filename, { type: 'image/jpeg' });
|
||||
} catch (error: unknown) {
|
||||
console.error('Detailed HEIC conversion error:', {
|
||||
error: error instanceof Error ? {
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
stack: error.stack
|
||||
} : error,
|
||||
uri: uri
|
||||
});
|
||||
throw new Error(`Failed to convert HEIC image: ${error instanceof Error ? error.message : 'An unknown error occurred'}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取指定时间范围内的媒体文件(包含 EXIF 信息)
|
||||
export const getMediaByDateRange = async (startDate: Date, endDate: Date) => {
|
||||
console.log("getMediaByDateRange", startDate.getTime(), endDate.getTime());
|
||||
|
||||
try {
|
||||
const { status } = await MediaLibrary.requestPermissionsAsync();
|
||||
if (status !== 'granted') {
|
||||
console.warn('Media library permission not granted');
|
||||
return [];
|
||||
}
|
||||
|
||||
// 获取媒体资源
|
||||
const media = await MediaLibrary.getAssetsAsync({
|
||||
mediaType: ['photo', 'video'],
|
||||
first: 10, // 每次最多获取100个
|
||||
first: 100,
|
||||
sortBy: [MediaLibrary.SortBy.creationTime],
|
||||
createdAfter: startDate.getTime(),
|
||||
createdBefore: endDate.getTime(),
|
||||
});
|
||||
|
||||
console.log("media1111111111", media);
|
||||
|
||||
// 为每个资源获取完整的 EXIF 信息
|
||||
const assetsWithExif = await Promise.all(
|
||||
media.assets.map(async (asset) => {
|
||||
try {
|
||||
if (asset.mediaType === 'photo') {
|
||||
const assetInfo = await MediaLibrary.getAssetInfoAsync(asset.id);
|
||||
return {
|
||||
...asset,
|
||||
exif: assetInfo.exif || null,
|
||||
location: assetInfo.location || null
|
||||
};
|
||||
}
|
||||
return asset; // 视频不处理 EXIF
|
||||
const assetInfo = await MediaLibrary.getAssetInfoAsync(asset.id);
|
||||
return {
|
||||
...asset,
|
||||
exif: assetInfo.exif || null,
|
||||
location: assetInfo.location || null
|
||||
};
|
||||
} catch (error) {
|
||||
return asset; // 如果获取 EXIF 失败,返回原始资源
|
||||
console.warn(`Failed to get EXIF for asset ${asset.id}:`, error);
|
||||
return asset;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return assetsWithExif;
|
||||
} catch (error) {
|
||||
console.error('Error in getMediaByDateRange:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@ -187,7 +260,7 @@ const checkMediaLibraryPermission = async (): Promise<{ hasPermission: boolean,
|
||||
};
|
||||
|
||||
// 处理单个媒体文件上传
|
||||
const processMediaUpload = async (asset: MediaLibrary.Asset) => {
|
||||
const processMediaUpload = async (asset: ExtendedAsset) => {
|
||||
try {
|
||||
// 检查权限
|
||||
const { hasPermission } = await checkMediaLibraryPermission();
|
||||
@ -199,28 +272,74 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => {
|
||||
|
||||
// 上传原始文件
|
||||
const uploadOriginalFile = async () => {
|
||||
const response = await fetch(asset.uri);
|
||||
const blob = await response.blob();
|
||||
const filename = asset.filename ||
|
||||
`${isVideo ? 'video' : 'image'}_${Date.now()}_original.${isVideo ? 'mp4' : 'jpg'}`;
|
||||
try {
|
||||
let fileToUpload: File;
|
||||
let mimeType = isVideo ? 'video/mp4' : 'image/jpeg';
|
||||
let filename = asset.filename ||
|
||||
`${isVideo ? 'video' : 'image'}_${Date.now()}_original.${isVideo ? 'mp4' : 'jpg'}`;
|
||||
|
||||
const mimeType = isVideo ? 'video/mp4' : 'image/jpeg';
|
||||
const file = new File([blob], filename, { type: mimeType });
|
||||
// 处理 HEIC 格式
|
||||
if (filename.toLowerCase().endsWith('.heic') || filename.toLowerCase().endsWith('.heif')) {
|
||||
try {
|
||||
fileToUpload = await convertHeicToJpeg(asset.uri);
|
||||
filename = filename.replace(/\.(heic|heif)$/i, '.jpg');
|
||||
} catch (conversionError) {
|
||||
console.error('HEIC conversion failed, trying fallback:', conversionError);
|
||||
// 如果转换失败,尝试使用 MediaLibrary 获取文件
|
||||
try {
|
||||
const assetInfo = await MediaLibrary.getAssetInfoAsync(asset.id);
|
||||
if (assetInfo.localUri) {
|
||||
const response = await fetch(assetInfo.localUri);
|
||||
const blob = await response.blob();
|
||||
fileToUpload = new File([blob], filename, { type: 'image/jpeg' });
|
||||
} else {
|
||||
throw new Error('No local URI available for the asset');
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error('Fallback method also failed:', fallbackError);
|
||||
// 如果所有方法都失败,尝试直接使用原始 URI
|
||||
const response = await fetch(asset.uri);
|
||||
const blob = await response.blob();
|
||||
fileToUpload = new File([blob], filename, { type: 'image/jpeg' });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 普通图片处理
|
||||
const response = await fetch(asset.uri);
|
||||
const blob = await response.blob();
|
||||
fileToUpload = new File([blob], filename, { type: mimeType });
|
||||
}
|
||||
let exifData = asset?.exif ? { ...transformData({ ...asset, exif: { ...asset?.exif, '{MakerApple}': undefined } }) } : {};
|
||||
const { upload_url, file_id } = await getUploadUrl(fileToUpload, {
|
||||
originalUri: asset.uri,
|
||||
creationTime: asset.creationTime,
|
||||
mediaType: isVideo ? 'video' : 'image',
|
||||
isCompressed: false,
|
||||
...exifData,
|
||||
GPSVersionID: undefined
|
||||
});
|
||||
|
||||
// 获取上传URL并上传原始文件
|
||||
const { upload_url, file_id } = await getUploadUrl(file, {
|
||||
originalUri: asset.uri,
|
||||
creationTime: asset.creationTime,
|
||||
mediaType: isVideo ? 'video' : 'image',
|
||||
isCompressed: false,
|
||||
...asset?.exif,
|
||||
GPSVersionID: undefined
|
||||
});
|
||||
await uploadFile(fileToUpload, upload_url);
|
||||
await confirmUpload(file_id);
|
||||
|
||||
await uploadFile(file, upload_url);
|
||||
await confirmUpload(file_id);
|
||||
|
||||
return { success: true, file_id };
|
||||
return { success: true, file_id };
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error('Error in uploadOriginalFile:', {
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
stack: error.stack,
|
||||
asset: {
|
||||
id: asset.id,
|
||||
uri: asset.uri,
|
||||
filename: asset.filename
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('An unknown error occurred:', error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 上传压缩文件(仅图片)
|
||||
@ -228,12 +347,14 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => {
|
||||
if (isVideo) return { success: true, file_id: null }; // 视频不压缩
|
||||
|
||||
try {
|
||||
const { file: compressedFile } = await compressImage(asset.uri);
|
||||
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' });
|
||||
|
||||
// 获取上传URL并上传压缩文件
|
||||
const { upload_url, file_id } = await getUploadUrl(compressedFile, {
|
||||
originalUri: asset.uri,
|
||||
creationTime: asset.creationTime,
|
||||
@ -249,14 +370,18 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 同时上传原始文件和压缩文件
|
||||
const [originalResult, compressedResult] = await Promise.all([
|
||||
uploadOriginalFile(),
|
||||
uploadCompressedFile()
|
||||
]);
|
||||
// 先上传原始文件
|
||||
const originalResult = await uploadOriginalFile();
|
||||
|
||||
// 如果是图片,再上传压缩文件
|
||||
let compressedResult = { success: true, file_id: null };
|
||||
if (!isVideo) {
|
||||
compressedResult = await uploadCompressedFile();
|
||||
}
|
||||
|
||||
// 添加素材
|
||||
addMaterial(originalResult.file_id, compressedResult?.file_id || '')
|
||||
addMaterial(originalResult.file_id, compressedResult?.file_id || '');
|
||||
|
||||
return {
|
||||
originalSuccess: originalResult.success,
|
||||
compressedSuccess: compressedResult.success,
|
||||
@ -272,7 +397,7 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => {
|
||||
return {
|
||||
originalSuccess: false,
|
||||
compressedSuccess: false,
|
||||
error
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -280,7 +405,7 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => {
|
||||
// 注册后台任务
|
||||
export const registerBackgroundUploadTask = async () => {
|
||||
try {
|
||||
// 检查是否已注册
|
||||
// 检查是否已经注册了任务
|
||||
const isRegistered = await TaskManager.isTaskRegisteredAsync(BACKGROUND_UPLOAD_TASK);
|
||||
if (isRegistered) {
|
||||
await BackgroundFetch.unregisterTaskAsync(BACKGROUND_UPLOAD_TASK);
|
||||
@ -288,13 +413,15 @@ export const registerBackgroundUploadTask = async () => {
|
||||
|
||||
// 注册后台任务
|
||||
await BackgroundFetch.registerTaskAsync(BACKGROUND_UPLOAD_TASK, {
|
||||
minimumInterval: 15 * 60, // 15分钟
|
||||
stopOnTerminate: false, // 应用关闭后继续运行
|
||||
startOnBoot: true, // 系统启动后自动运行
|
||||
minimumInterval: 15 * 60, // 15 分钟
|
||||
stopOnTerminate: false, // 应用退出后继续运行
|
||||
startOnBoot: true, // 设备启动后自动启动
|
||||
});
|
||||
|
||||
console.log('Background task registered');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error registering background task:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -302,63 +429,28 @@ export const registerBackgroundUploadTask = async () => {
|
||||
// 定义后台任务
|
||||
TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => {
|
||||
try {
|
||||
// 检查并请求媒体库权限
|
||||
const { hasPermission } = await checkMediaLibraryPermission();
|
||||
if (!hasPermission) {
|
||||
return BackgroundFetch.BackgroundFetchResult.NoData;
|
||||
}
|
||||
|
||||
// 获取过去24小时内的媒体
|
||||
const now = new Date();
|
||||
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
|
||||
const media = await getMediaByDateRange(yesterday, now);
|
||||
// 获取最近24小时的媒体文件
|
||||
const media = await getMediaByDateRange(oneDayAgo, now);
|
||||
|
||||
if (media.length === 0) {
|
||||
console.log('No media files to upload');
|
||||
return BackgroundFetch.BackgroundFetchResult.NoData;
|
||||
}
|
||||
|
||||
// 创建并发限制器
|
||||
const limit = pLimit(CONCURRENCY_LIMIT);
|
||||
// 处理媒体文件上传
|
||||
const results = await triggerManualUpload(oneDayAgo, now);
|
||||
const successCount = results.filter(r => r.originalSuccess).length;
|
||||
|
||||
// 准备所有上传任务
|
||||
const uploadPromises = media.map(item =>
|
||||
limit(async () => {
|
||||
try {
|
||||
await processMediaUpload(item);
|
||||
return { success: true, id: item.id };
|
||||
} catch (error: any) {
|
||||
if (error.message === 'No media library permission') {
|
||||
throw error; // 权限错误直接抛出
|
||||
}
|
||||
return { success: false, id: item.id, error };
|
||||
}
|
||||
})
|
||||
);
|
||||
console.log(`Background upload completed. Success: ${successCount}/${results.length}`);
|
||||
|
||||
// 等待所有上传任务完成
|
||||
const results = await Promise.allSettled(uploadPromises);
|
||||
|
||||
// 统计结果
|
||||
const succeeded = results.filter(r =>
|
||||
r.status === 'fulfilled' && r.value.success
|
||||
).length;
|
||||
const failed = results.length - succeeded;
|
||||
|
||||
// 如果有权限错误,返回失败
|
||||
const hasPermissionError = results.some(r =>
|
||||
r.status === 'rejected' ||
|
||||
(r.status === 'fulfilled' && r.value.error?.message === 'No media library permission')
|
||||
);
|
||||
|
||||
if (hasPermissionError) {
|
||||
return BackgroundFetch.BackgroundFetchResult.Failed;
|
||||
}
|
||||
|
||||
return succeeded > 0 ?
|
||||
BackgroundFetch.BackgroundFetchResult.NewData :
|
||||
BackgroundFetch.BackgroundFetchResult.NoData;
|
||||
return successCount > 0
|
||||
? BackgroundFetch.BackgroundFetchResult.NewData
|
||||
: BackgroundFetch.BackgroundFetchResult.NoData;
|
||||
} catch (error) {
|
||||
console.error('Background task error:', error);
|
||||
return BackgroundFetch.BackgroundFetchResult.Failed;
|
||||
}
|
||||
});
|
||||
@ -372,30 +464,65 @@ export const triggerManualUpload = async (startDate: Date, endDate: Date) => {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 分离图片和视频
|
||||
const photos = media.filter(item => item.mediaType === 'photo');
|
||||
const videos = media.filter(item => item.mediaType === 'video');
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const item of media) {
|
||||
// 先处理所有图片
|
||||
for (const item of photos) {
|
||||
try {
|
||||
const result = await processMediaUpload(item);
|
||||
results.push({
|
||||
id: item.id,
|
||||
originalSuccess: result.originalSuccess,
|
||||
compressedSuccess: result.compressedSuccess,
|
||||
fileIds: result.fileIds,
|
||||
...result
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
|
||||
console.error('Error processing photo upload:', {
|
||||
error: error instanceof Error ? {
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
stack: error.stack
|
||||
} : error,
|
||||
assetId: item.id
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
id: item.id,
|
||||
originalSuccess: false,
|
||||
compressedSuccess: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
error: errorMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const originalSuccessCount = results.filter(r => r.originalSuccess).length;
|
||||
const compressedSuccessCount = results.filter(r => r.compressedSuccess).length;
|
||||
Alert.alert('上传完成', `成功上传 ${originalSuccessCount}/${media.length} 个原始文件,${compressedSuccessCount}/${media.length} 个压缩文件`);
|
||||
// 再处理所有视频
|
||||
for (const item of videos) {
|
||||
try {
|
||||
const result = await processMediaUpload(item);
|
||||
results.push({
|
||||
id: item.id,
|
||||
...result
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
|
||||
console.error('Error processing video upload:', {
|
||||
error: error instanceof Error ? {
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
stack: error.stack
|
||||
} : error,
|
||||
assetId: item.id
|
||||
});
|
||||
results.push({
|
||||
id: item.id,
|
||||
originalSuccess: false,
|
||||
compressedSuccess: false,
|
||||
error: errorMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
|
||||
42
components/utils/objectFlat.ts
Normal file
42
components/utils/objectFlat.ts
Normal file
@ -0,0 +1,42 @@
|
||||
interface RawData {
|
||||
uri: string;
|
||||
exif?: Record<string, any>;
|
||||
location?: {
|
||||
latitude?: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
export function transformData(data: RawData): RawData {
|
||||
const result = { ...data };
|
||||
|
||||
if (result.exif) {
|
||||
const newExif: Record<string, any> = {};
|
||||
|
||||
for (const key in result.exif) {
|
||||
const value = result.exif[key];
|
||||
|
||||
// 普通对象:{Exif}, {TIFF}, {XMP} 等
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
if (key === '{GPS}') {
|
||||
// 处理 GPS 字段:所有子字段加前缀 "GPS"
|
||||
for (const subKey in value) {
|
||||
const newKey = 'GPS' + subKey; // 所有字段都加前缀
|
||||
newExif[newKey] = value[subKey];
|
||||
}
|
||||
} else {
|
||||
// 其它嵌套对象直接展开字段
|
||||
for (const subKey in value) {
|
||||
newExif[subKey] = value[subKey];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 非对象字段保留原样
|
||||
newExif[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
result.exif = newExif;
|
||||
}
|
||||
// 最后将result的exif信息平铺
|
||||
return { ...result, ...result.exif, exif: undefined };
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user