Move to a layer that should be dealing with the piexifjs internals

This commit is contained in:
Manav Rathi
2024-07-22 15:23:37 +05:30
parent 3b1fd78fbe
commit cff6570ebb
3 changed files with 69 additions and 68 deletions

View File

@@ -4,6 +4,7 @@ import { FILE_TYPE } from "@/media/file-type";
import { decodeLivePhoto } from "@/media/live-photo";
import type { Metadata } from "@/media/types/file";
import downloadManager from "@/new/photos/services/download";
import { updateExifIfNeededAndPossible } from "@/new/photos/services/exif-update";
import {
exportMetadataDirectoryName,
exportTrashDirectoryName,
@@ -36,7 +37,7 @@ import {
getCollectionUserFacingName,
getNonEmptyPersonalCollections,
} from "utils/collection";
import { getPersonalFiles, updateExifIfNeeded } from "utils/file";
import { getPersonalFiles } from "utils/file";
import { getAllLocalCollections } from "../collectionService";
import { migrateExport } from "./migration";
@@ -970,7 +971,7 @@ class ExportService {
try {
const fileUID = getExportRecordFileUID(file);
const originalFileStream = await downloadManager.getFile(file);
const updatedFileStream = await updateExifIfNeeded(
const updatedFileStream = await updateExifIfNeededAndPossible(
file,
originalFileStream,
);

View File

@@ -1,10 +1,9 @@
import { lowercaseExtension } from "@/base/file";
import log from "@/base/log";
import { type Electron } from "@/base/types/ipc";
import { FILE_TYPE } from "@/media/file-type";
import { decodeLivePhoto } from "@/media/live-photo";
import DownloadManager from "@/new/photos/services/download";
import { setJPEGExifDateTimeOriginal } from "@/new/photos/services/exif-update";
import { updateExifIfNeededAndPossible } from "@/new/photos/services/exif-update";
import {
EncryptedEnteFile,
EnteFile,
@@ -74,7 +73,7 @@ export async function downloadFile(file: EnteFile) {
new File([fileBlob], file.metadata.title),
);
fileBlob = await new Response(
await updateExifIfNeeded(file, fileBlob.stream()),
await updateExifIfNeededAndPossible(file, fileBlob.stream()),
).blob();
fileBlob = new Blob([fileBlob], { type: fileType.mimeType });
const tempURL = URL.createObjectURL(fileBlob);
@@ -86,64 +85,6 @@ export async function downloadFile(file: EnteFile) {
}
}
/**
* Return a new stream after applying Exif updates if applicable to the given
* stream, otherwise return the original.
*
* This function is meant to provide a stream that can be used to download (or
* export) a file to the user's computer after applying any Exif updates to the
* original file's data.
*
* - This only updates JPEG files.
*
* - For JPEG files, the DateTimeOriginal Exif entry is updated to reflect the
* time that the user edited within Ente.
*
* @param enteFile The {@link EnteFile} whose data we want.
*
* @param stream A {@link ReadableStream} containing the original data for
* {@link enteFile}.
*
* @returns A new {@link ReadableStream} with updates if any updates were
* needed, otherwise return the original stream.
*/
export const updateExifIfNeeded = async (
enteFile: EnteFile,
stream: ReadableStream<Uint8Array>,
): Promise<ReadableStream<Uint8Array>> => {
// Not an image.
if (enteFile.metadata.fileType != FILE_TYPE.IMAGE) return stream;
// Time was not edited.
if (!enteFile.pubMagicMetadata?.data.editedTime) return stream;
const fileName = enteFile.metadata.title;
const extension = lowercaseExtension(fileName);
// Not a JPEG (likely).
if (extension != "jpeg" && extension != "jpg") return stream;
const blob = await new Response(stream).blob();
try {
const updatedBlob = await setJPEGExifDateTimeOriginal(
blob,
new Date(enteFile.pubMagicMetadata.data.editedTime / 1000),
);
return updatedBlob.stream();
} catch (e) {
log.error(`Failed to modify Exif date for ${fileName}`, e);
// We used the file's extension to determine if this was a JPEG, but
// this is not a guarantee. Misnamed files, while rare, do exist. So in
// that is the error thrown by the underlying library, fallback to the
// original instead of causing the entire download or export to fail.
if (
e instanceof Error &&
e.message.endsWith("Given file is neither JPEG nor TIFF")
) {
return blob.stream();
}
throw e;
}
};
/** Segment the given {@link files} into lists indexed by their collection ID */
export const groupFilesBasedOnCollectionID = (files: EnteFile[]) => {
const result = new Map<number, EnteFile[]>();
@@ -507,7 +448,7 @@ async function downloadFileDesktop(
const fs = electron.fs;
const stream = await DownloadManager.getFile(file);
const updatedStream = await updateExifIfNeeded(file, stream);
const updatedStream = await updateExifIfNeededAndPossible(file, stream);
if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
const fileBlob = await new Response(updatedStream).blob();

View File

@@ -1,4 +1,66 @@
import { lowercaseExtension } from "@/base/file";
import log from "@/base/log";
import { FILE_TYPE } from "@/media/file-type";
import piexif from "piexifjs";
import type { EnteFile } from "../types/file";
/**
* Return a new stream after applying Exif updates if applicable to the given
* stream, otherwise return the original.
*
* This function is meant to provide a stream that can be used to download (or
* export) a file to the user's computer after applying any Exif updates to the
* original file's data.
*
* - This only updates JPEG files.
*
* - For JPEG files, the DateTimeOriginal Exif entry is updated to reflect the
* time that the user edited within Ente.
*
* @param enteFile The {@link EnteFile} whose data we want.
*
* @param stream A {@link ReadableStream} containing the original data for
* {@link enteFile}.
*
* @returns A new {@link ReadableStream} with updates if any updates were
* needed, otherwise return the original stream.
*/
export const updateExifIfNeededAndPossible = async (
enteFile: EnteFile,
stream: ReadableStream<Uint8Array>,
): Promise<ReadableStream<Uint8Array>> => {
// Not an image.
if (enteFile.metadata.fileType != FILE_TYPE.IMAGE) return stream;
// Time was not edited.
if (!enteFile.pubMagicMetadata?.data.editedTime) return stream;
const fileName = enteFile.metadata.title;
const extension = lowercaseExtension(fileName);
// Not a JPEG (likely).
if (extension != "jpeg" && extension != "jpg") return stream;
const blob = await new Response(stream).blob();
try {
const updatedBlob = await setJPEGExifDateTimeOriginal(
blob,
new Date(enteFile.pubMagicMetadata.data.editedTime / 1000),
);
return updatedBlob.stream();
} catch (e) {
log.error(`Failed to modify Exif date for ${fileName}`, e);
// We used the file's extension to determine if this was a JPEG, but
// this is not a guarantee. Misnamed files, while rare, do exist. So in
// that is the error thrown by the underlying library, fallback to the
// original instead of causing the entire download or export to fail.
if (
e instanceof Error &&
e.message.endsWith("Given file is neither JPEG nor TIFF")
) {
return blob.stream();
}
throw e;
}
};
/**
* Return a new blob with the "DateTimeOriginal" Exif tag set to the given
@@ -11,10 +73,7 @@ import piexif from "piexifjs";
*
* @returns A new blob derived from {@link jpegBlob} but with the updated date.
*/
export const setJPEGExifDateTimeOriginal = async (
jpegBlob: Blob,
date: Date,
) => {
const setJPEGExifDateTimeOriginal = async (jpegBlob: Blob, date: Date) => {
let dataURL = await blobToDataURL(jpegBlob);
// Since we pass a Blob without an associated type, we get back a generic
// data URL of the form "data:application/octet-stream;base64,...".