remove metadata preprocessing

This commit is contained in:
Abhinav
2022-12-12 20:53:54 +05:30
parent 964ee4b880
commit 6aed8d3002
6 changed files with 96 additions and 276 deletions

View File

@@ -69,9 +69,9 @@ export async function readFile(
export async function extractFileMetadata(
parsedMetadataJSONMap: ParsedMetadataJSONMap,
rawFile: File | ElectronFile,
collectionID: number,
fileTypeInfo: FileTypeInfo
fileTypeInfo: FileTypeInfo,
rawFile: File | ElectronFile
) {
const originalName = getFileOriginalName(rawFile);
const googleMetadata =

View File

@@ -1,20 +1,20 @@
import { FILE_TYPE } from 'constants/file';
import { LIVE_PHOTO_ASSET_SIZE_LIMIT } from 'constants/upload';
import { encodeMotionPhoto } from 'services/motionPhotoService';
import { getFileType } from 'services/typeDetectionService';
import {
ElectronFile,
FileTypeInfo,
FileWithCollection,
LivePhotoAssets,
Metadata,
ParsedMetadataJSONMap,
} from 'types/upload';
import { CustomError } from 'utils/error';
import { isImageOrVideo, splitFilenameAndExtension } from 'utils/file';
import { logError } from 'utils/sentry';
import { getUint8ArrayView } from '../readerService';
import { extractFileMetadata } from './fileService';
import { generateThumbnail } from './thumbnailService';
import uploadService from './uploadService';
import UploadService from './uploadService';
interface LivePhotoIdentifier {
collectionID: number;
@@ -23,37 +23,54 @@ interface LivePhotoIdentifier {
size: number;
}
interface Asset {
file: File | ElectronFile;
metadata: Metadata;
fileTypeInfo: FileTypeInfo;
}
const ENTE_LIVE_PHOTO_FORMAT = 'elp';
const UNDERSCORE_THREE = '_3';
const UNDERSCORE = '_';
export function getLivePhotoFileType(
imageFileTypeInfo: FileTypeInfo,
videoTypeInfo: FileTypeInfo
): FileTypeInfo {
export async function getLivePhotoFileType(
livePhotoAssets: LivePhotoAssets
): Promise<FileTypeInfo> {
const imageFileTypeInfo = await getFileType(livePhotoAssets.image);
const videoFileTypeInfo = await getFileType(livePhotoAssets.video);
return {
fileType: FILE_TYPE.LIVE_PHOTO,
exactType: `${imageFileTypeInfo.exactType}+${videoTypeInfo.exactType}`,
exactType: `${imageFileTypeInfo.exactType}+${videoFileTypeInfo.exactType}`,
imageType: imageFileTypeInfo.exactType,
videoType: videoTypeInfo.exactType,
videoType: videoFileTypeInfo.exactType,
};
}
export function getLivePhotoMetadata(
imageMetadata: Metadata,
videoMetadata: Metadata
export async function extractLivePhotoMetadata(
parsedMetadataJSONMap: ParsedMetadataJSONMap,
collectionID: number,
fileTypeInfo: FileTypeInfo,
livePhotoAssets: LivePhotoAssets
) {
const imageFileTypeInfo: FileTypeInfo = {
fileType: FILE_TYPE.IMAGE,
exactType: fileTypeInfo.imageType,
};
const videoFileTypeInfo: FileTypeInfo = {
fileType: FILE_TYPE.VIDEO,
exactType: fileTypeInfo.videoType,
};
const imageMetadata = await extractFileMetadata(
parsedMetadataJSONMap,
collectionID,
imageFileTypeInfo,
livePhotoAssets.image
);
const videoMetadata = await extractFileMetadata(
parsedMetadataJSONMap,
collectionID,
videoFileTypeInfo,
livePhotoAssets.video
);
return {
...imageMetadata,
title: getLivePhotoName(imageMetadata.title),
title: getLivePhotoName(livePhotoAssets),
fileType: FILE_TYPE.LIVE_PHOTO,
imageHash: imageMetadata.hash,
videoHash: videoMetadata.hash,
@@ -61,17 +78,13 @@ export function getLivePhotoMetadata(
};
}
export function getLivePhotoFilePath(imageAsset: Asset): string {
return getLivePhotoName((imageAsset.file as any).path);
}
export function getLivePhotoSize(livePhotoAssets: LivePhotoAssets) {
return livePhotoAssets.image.size + livePhotoAssets.video.size;
}
export function getLivePhotoName(imageTitle: string) {
export function getLivePhotoName(livePhotoAssets: LivePhotoAssets) {
return `${
splitFilenameAndExtension(imageTitle)[0]
splitFilenameAndExtension(livePhotoAssets.image.name)[0]
}.${ENTE_LIVE_PHOTO_FORMAT}`;
}
@@ -103,7 +116,7 @@ export async function readLivePhoto(
};
}
export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) {
export async function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) {
try {
const analysedMediaFiles: FileWithCollection[] = [];
mediaFiles
@@ -122,18 +135,8 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) {
while (index < mediaFiles.length - 1) {
const firstMediaFile = mediaFiles[index];
const secondMediaFile = mediaFiles[index + 1];
const {
fileTypeInfo: firstFileTypeInfo,
metadata: firstFileMetadata,
} = UploadService.getFileMetadataAndFileTypeInfo(
firstMediaFile.localID
);
const {
fileTypeInfo: secondFileFileInfo,
metadata: secondFileMetadata,
} = UploadService.getFileMetadataAndFileTypeInfo(
secondMediaFile.localID
);
const firstFileTypeInfo = await getFileType(firstMediaFile.file);
const secondFileFileInfo = await getFileType(secondMediaFile.file);
const firstFileIdentifier: LivePhotoIdentifier = {
collectionID: firstMediaFile.collectionID,
fileType: firstFileTypeInfo.fileType,
@@ -146,33 +149,23 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) {
name: secondMediaFile.file.name,
size: secondMediaFile.file.size,
};
const firstAsset = {
file: firstMediaFile.file,
metadata: firstFileMetadata,
fileTypeInfo: firstFileTypeInfo,
};
const secondAsset = {
file: secondMediaFile.file,
metadata: secondFileMetadata,
fileTypeInfo: secondFileFileInfo,
};
if (
areFilesLivePhotoAssets(
firstFileIdentifier,
secondFileIdentifier
)
) {
let imageAsset: Asset;
let videoAsset: Asset;
let imageFile: File | ElectronFile;
let videoFile: File | ElectronFile;
if (
firstFileTypeInfo.fileType === FILE_TYPE.IMAGE &&
secondFileFileInfo.fileType === FILE_TYPE.VIDEO
) {
imageAsset = firstAsset;
videoAsset = secondAsset;
imageFile = firstMediaFile.file;
videoFile = secondMediaFile.file;
} else {
videoAsset = firstAsset;
imageAsset = secondAsset;
videoFile = firstMediaFile.file;
imageFile = secondMediaFile.file;
}
const livePhotoLocalID = firstMediaFile.localID;
analysedMediaFiles.push({
@@ -180,25 +173,10 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) {
collectionID: firstMediaFile.collectionID,
isLivePhoto: true,
livePhotoAssets: {
image: imageAsset.file,
video: videoAsset.file,
image: imageFile,
video: videoFile,
},
});
const livePhotoFileTypeInfo: FileTypeInfo =
getLivePhotoFileType(
imageAsset.fileTypeInfo,
videoAsset.fileTypeInfo
);
const livePhotoMetadata: Metadata = getLivePhotoMetadata(
imageAsset.metadata,
videoAsset.metadata
);
const livePhotoPath = getLivePhotoFilePath(imageAsset);
uploadService.setFileMetadataAndFileTypeInfo(livePhotoLocalID, {
fileTypeInfo: { ...livePhotoFileTypeInfo },
metadata: { ...livePhotoMetadata },
filePath: livePhotoPath,
});
index += 2;
} else {
analysedMediaFiles.push({

View File

@@ -20,22 +20,13 @@ import { CustomError } from 'utils/error';
import { Collection } from 'types/collection';
import { EnteFile } from 'types/file';
import {
ElectronFile,
FileWithCollection,
Metadata,
MetadataAndFileTypeInfo,
MetadataAndFileTypeInfoMap,
ParsedMetadataJSON,
ParsedMetadataJSONMap,
PublicUploadProps,
} from 'types/upload';
import {
UPLOAD_RESULT,
MAX_FILE_SIZE_SUPPORTED,
UPLOAD_STAGES,
} from 'constants/upload';
import { UPLOAD_RESULT, UPLOAD_STAGES } from 'constants/upload';
import { ComlinkWorker } from 'utils/comlink';
import { FILE_TYPE } from 'constants/file';
import uiService from './uiService';
import { addLogLine, getFileNameSize } from 'utils/logging';
import isElectron from 'is-electron';
@@ -54,7 +45,6 @@ const FILE_UPLOAD_COMPLETED = 100;
class UploadManager {
private cryptoWorkers = new Array<ComlinkWorker>(MAX_CONCURRENT_UPLOADS);
private parsedMetadataJSONMap: ParsedMetadataJSONMap;
private metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap;
private filesToBeUploaded: FileWithCollection[];
private remainingFiles: FileWithCollection[] = [];
private failedFiles: FileWithCollection[];
@@ -85,10 +75,7 @@ class UploadManager {
this.remainingFiles = [];
this.failedFiles = [];
this.parsedMetadataJSONMap = new Map<string, ParsedMetadataJSON>();
this.metadataAndFileTypeInfoMap = new Map<
number,
MetadataAndFileTypeInfo
>();
this.uploaderName = null;
}
@@ -154,50 +141,18 @@ class UploadManager {
);
}
if (mediaFiles.length) {
UIService.setUploadStage(UPLOAD_STAGES.EXTRACTING_METADATA);
await this.extractMetadataFromFiles(mediaFiles);
UploadService.setMetadataAndFileTypeInfoMap(
this.metadataAndFileTypeInfoMap
);
// filter out files whose metadata detection failed or those that have been skipped because the files are too large,
// as they will be rejected during upload and are not valid upload files which we need to clustering
const rejectedFileLocalIDs = new Set(
[...this.metadataAndFileTypeInfoMap.entries()].map(
([localID, metadataAndFileTypeInfo]) => {
if (
!metadataAndFileTypeInfo.metadata ||
!metadataAndFileTypeInfo.fileTypeInfo
) {
return localID;
}
}
)
);
const rejectedFiles = [];
const filesWithMetadata = [];
mediaFiles.forEach((m) => {
if (rejectedFileLocalIDs.has(m.localID)) {
rejectedFiles.push(m);
} else {
filesWithMetadata.push(m);
}
});
addLogLine(`clusterLivePhotoFiles started`);
const analysedMediaFiles =
UploadService.clusterLivePhotoFiles(filesWithMetadata);
await UploadService.clusterLivePhotoFiles(mediaFiles);
addLogLine(`clusterLivePhotoFiles ended`);
const allFiles = [...rejectedFiles, ...analysedMediaFiles];
addLogLine(
`got live photos: ${mediaFiles.length !== allFiles.length}`
`got live photos: ${
mediaFiles.length !== analysedMediaFiles.length
}`
);
uiService.setFilenames(
new Map<number, string>(
allFiles.map((mediaFile) => [
analysedMediaFiles.map((mediaFile) => [
mediaFile.localID,
UploadService.getAssetName(mediaFile),
])
@@ -205,10 +160,10 @@ class UploadManager {
);
UIService.setHasLivePhoto(
mediaFiles.length !== allFiles.length
mediaFiles.length !== analysedMediaFiles.length
);
await this.uploadMediaFiles(allFiles);
await this.uploadMediaFiles(analysedMediaFiles);
}
} catch (e) {
if (e.message === CustomError.UPLOAD_CANCELLED) {
@@ -293,104 +248,6 @@ class UploadManager {
}
}
private async extractMetadataFromFiles(mediaFiles: FileWithCollection[]) {
try {
addLogLine(`extractMetadataFromFiles executed`);
UIService.reset(mediaFiles.length);
for (const { file, localID, collectionID } of mediaFiles) {
UIService.setFileProgress(localID, 0);
if (uploadCancelService.isUploadCancelationRequested()) {
throw Error(CustomError.UPLOAD_CANCELLED);
}
let fileTypeInfo = null;
let metadata = null;
let filePath = null;
try {
addLogLine(
`metadata extraction started ${getFileNameSize(file)} `
);
const result = await this.extractFileTypeAndMetadata(
file,
collectionID,
this.publicUploadProps?.accessedThroughSharedURL
);
fileTypeInfo = result.fileTypeInfo;
metadata = result.metadata;
filePath = result.filePath;
addLogLine(
`metadata extraction successful${getFileNameSize(
file
)} `
);
} catch (e) {
if (e.message === CustomError.UPLOAD_CANCELLED) {
throw e;
} else {
// and don't break for subsequent files just log and move on
logError(e, 'extractFileTypeAndMetadata failed');
addLogLine(
`metadata extraction failed ${getFileNameSize(
file
)} error: ${e.message}`
);
}
}
this.metadataAndFileTypeInfoMap.set(localID, {
fileTypeInfo: fileTypeInfo && { ...fileTypeInfo },
metadata: metadata && { ...metadata },
filePath: filePath,
});
UIService.removeFromInProgressList(localID);
UIService.increaseFileUploaded();
}
} catch (e) {
if (e.message !== CustomError.UPLOAD_CANCELLED) {
logError(e, 'error extracting metadata');
}
throw e;
}
}
private async extractFileTypeAndMetadata(
file: File | ElectronFile,
collectionID: number,
skipVideos: boolean
) {
if (file.size >= MAX_FILE_SIZE_SUPPORTED) {
addLogLine(
`${getFileNameSize(file)} rejected because of large size`
);
return { fileTypeInfo: null, metadata: null };
}
const fileTypeInfo = await UploadService.getFileType(file);
if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) {
addLogLine(
`${getFileNameSize(
file
)} rejected because of unknown file format`
);
return { fileTypeInfo, metadata: null };
}
if (skipVideos && fileTypeInfo.fileType === FILE_TYPE.VIDEO) {
return { fileTypeInfo, metadata: null };
}
addLogLine(` extracting ${getFileNameSize(file)} metadata`);
let metadata: Metadata;
try {
metadata = await UploadService.extractFileMetadata(
file,
collectionID,
fileTypeInfo
);
const filePath = (file as any).path as string;
return { fileTypeInfo, metadata, filePath };
} catch (e) {
logError(e, 'failed to extract file metadata');
return { fileTypeInfo, metadata: null, filePath: null };
}
}
private async uploadMediaFiles(mediaFiles: FileWithCollection[]) {
addLogLine(`uploadMediaFiles called`);
this.filesToBeUploaded = [...this.filesToBeUploaded, ...mediaFiles];

View File

@@ -7,15 +7,12 @@ import { CustomError, handleUploadError } from 'utils/error';
import {
B64EncryptionResult,
BackupedFile,
ElectronFile,
EncryptedFile,
FileTypeInfo,
FileWithCollection,
FileWithMetadata,
isDataStream,
Metadata,
MetadataAndFileTypeInfo,
MetadataAndFileTypeInfoMap,
ParsedMetadataJSON,
ParsedMetadataJSONMap,
ProcessedFile,
@@ -26,6 +23,8 @@ import {
} from 'types/upload';
import {
clusterLivePhotoFiles,
extractLivePhotoMetadata,
getLivePhotoFileType,
getLivePhotoName,
getLivePhotoSize,
readLivePhoto,
@@ -42,10 +41,6 @@ class UploadService {
string,
ParsedMetadataJSON
>();
private metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap = new Map<
number,
MetadataAndFileTypeInfo
>();
private uploaderName: string;
@@ -74,12 +69,6 @@ class UploadService {
return this.uploaderName;
}
setMetadataAndFileTypeInfoMap(
metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap
) {
this.metadataAndFileTypeInfoMap = metadataAndFileTypeInfoMap;
}
reducePendingUploadCount() {
this.pendingUploadCount--;
}
@@ -90,14 +79,16 @@ class UploadService {
: getFileSize(file);
}
getAssetName({ isLivePhoto, file, livePhotoAssets }: FileWithCollection) {
getAssetName({ isLivePhoto, file, livePhotoAssets }: UploadAsset) {
return isLivePhoto
? getLivePhotoName(livePhotoAssets.image.name)
? getLivePhotoName(livePhotoAssets)
: getFilename(file);
}
async getFileType(file: File | ElectronFile) {
return getFileType(file);
getAssetFileType({ isLivePhoto, file, livePhotoAssets }: UploadAsset) {
return isLivePhoto
? getLivePhotoFileType(livePhotoAssets)
: getFileType(file);
}
async readAsset(
@@ -109,31 +100,24 @@ class UploadService {
: await readFile(fileTypeInfo, file);
}
async extractFileMetadata(
file: File | ElectronFile,
async extractAssetMetadata(
{ isLivePhoto, file, livePhotoAssets }: UploadAsset,
collectionID: number,
fileTypeInfo: FileTypeInfo
): Promise<Metadata> {
return extractFileMetadata(
this.parsedMetadataJSONMap,
file,
collectionID,
fileTypeInfo
);
}
getFileMetadataAndFileTypeInfo(localID: number) {
return this.metadataAndFileTypeInfoMap.get(localID);
}
setFileMetadataAndFileTypeInfo(
localID: number,
metadataAndFileTypeInfo: MetadataAndFileTypeInfo
) {
return this.metadataAndFileTypeInfoMap.set(
localID,
metadataAndFileTypeInfo
);
return isLivePhoto
? await extractFileMetadata(
this.parsedMetadataJSONMap,
collectionID,
fileTypeInfo,
file
)
: extractLivePhotoMetadata(
this.parsedMetadataJSONMap,
collectionID,
fileTypeInfo,
livePhotoAssets
);
}
clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) {

View File

@@ -6,7 +6,12 @@ import UIService from './uiService';
import UploadService from './uploadService';
import { FILE_TYPE } from 'constants/file';
import { UPLOAD_RESULT, MAX_FILE_SIZE_SUPPORTED } from 'constants/upload';
import { FileWithCollection, BackupedFile, UploadFile } from 'types/upload';
import {
FileWithCollection,
BackupedFile,
UploadFile,
FileTypeInfo,
} from 'types/upload';
import { addLocalLog, addLogLine } from 'utils/logging';
import { convertBytesToHumanReadable } from 'utils/file/size';
import { sleep } from 'utils/common';
@@ -33,22 +38,24 @@ export default async function uploader(
addLogLine(`uploader called for ${fileNameSize}`);
UIService.setFileProgress(localID, 0);
await sleep(0);
const { fileTypeInfo, metadata } =
UploadService.getFileMetadataAndFileTypeInfo(localID);
let fileTypeInfo: FileTypeInfo;
try {
const fileSize = UploadService.getAssetSize(uploadAsset);
if (fileSize >= MAX_FILE_SIZE_SUPPORTED) {
return { fileUploadResult: UPLOAD_RESULT.TOO_LARGE };
}
fileTypeInfo = await UploadService.getAssetFileType(uploadAsset);
if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) {
throw Error(CustomError.UNSUPPORTED_FILE_FORMAT);
}
if (skipVideos && fileTypeInfo.fileType === FILE_TYPE.VIDEO) {
return { fileUploadResult: UPLOAD_RESULT.SKIPPED_VIDEOS };
}
if (!metadata) {
throw Error(CustomError.NO_METADATA);
}
const metadata = await UploadService.extractAssetMetadata(
uploadAsset,
collection.id,
fileTypeInfo
);
const matchingExistingFiles = findMatchingExistingFiles(
existingFiles,

View File

@@ -90,13 +90,7 @@ export interface FileWithCollection extends UploadAsset {
collection?: Collection;
collectionID?: number;
}
export interface MetadataAndFileTypeInfo {
metadata: Metadata;
fileTypeInfo: FileTypeInfo;
filePath: string;
}
export type MetadataAndFileTypeInfoMap = Map<number, MetadataAndFileTypeInfo>;
export type ParsedMetadataJSONMap = Map<string, ParsedMetadataJSON>;
export interface UploadURL {