Merge branch 'main' into image-exif-aspect-ratio
This commit is contained in:
commit
c94bf73ebf
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "5000 characters max",
|
||||
"DATE_TIME_ORIGINAL": "EXIF:DateTimeOriginal",
|
||||
"DATE_TIME_DIGITIZED": "EXIF:DateTimeDigitized",
|
||||
"METADATA_DATE":"EXIF:MetadataDate",
|
||||
"CUSTOM_TIME": "Custom time",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "Re-open plans",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "Failed to open plans",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "Máximo 5000 caracteres",
|
||||
"DATE_TIME_ORIGINAL": "EXIF: Fecha original",
|
||||
"DATE_TIME_DIGITIZED": "EXIF: Fecha Digitalizado",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "Hora personalizada",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "Reabrir planes",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "Error al abrir los planes",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "5000 caractères max",
|
||||
"DATE_TIME_ORIGINAL": "EXIF:DateTimeOriginal",
|
||||
"DATE_TIME_DIGITIZED": "EXIF:DateTimeDigitized",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "Heure personnalisée",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "Rouvrir les plans",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "Échec pour rouvrir les plans",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "5000 tekens max",
|
||||
"DATE_TIME_ORIGINAL": "EXIF:DatumTijdOrigineel",
|
||||
"DATE_TIME_DIGITIZED": "EXIF:DatumTijdDigitaliseerd",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "Aangepaste tijd",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "Abonnementen heropenen",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "Kon abonnementen niet openen",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "",
|
||||
"DATE_TIME_ORIGINAL": "",
|
||||
"DATE_TIME_DIGITIZED": "",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
|
||||
|
|
|
@ -340,6 +340,7 @@
|
|||
"CAPTION_CHARACTER_LIMIT": "5000个字符上限",
|
||||
"DATE_TIME_ORIGINAL": "EXIF:日期 时间 原始文件",
|
||||
"DATE_TIME_DIGITIZED": "EXIF:日期 时间 数字化",
|
||||
"METADATA_DATE": "",
|
||||
"CUSTOM_TIME": "自定义时间",
|
||||
"REOPEN_PLAN_SELECTOR_MODAL": "重新启动计划",
|
||||
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "未能打开计划",
|
||||
|
|
|
@ -29,6 +29,7 @@ export enum FIX_STATE {
|
|||
export enum FIX_OPTIONS {
|
||||
DATE_TIME_ORIGINAL,
|
||||
DATE_TIME_DIGITIZED,
|
||||
METADATA_DATE,
|
||||
CUSTOM_TIME,
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,14 @@ export default function FixCreationTimeOptions({ handleChange, values }) {
|
|||
selected={Number(values.option)}
|
||||
/>
|
||||
</Row>
|
||||
<Row style={{ margin: '0' }}>
|
||||
<Option
|
||||
value={FIX_OPTIONS.METADATA_DATE}
|
||||
onChange={handleChange('option')}
|
||||
label={t('METADATA_DATE')}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Row>
|
||||
<Row style={{ margin: '0' }}>
|
||||
<Value width="50%">
|
||||
<Option
|
||||
|
|
|
@ -428,7 +428,10 @@ const PhotoFrame = ({
|
|||
`[${item.id}] gallery context cache miss, calling downloadManager to get file`
|
||||
);
|
||||
appContext.startLoading();
|
||||
let downloadedURL;
|
||||
let downloadedURL: {
|
||||
original: string[];
|
||||
converted: string[];
|
||||
};
|
||||
if (publicCollectionGalleryContext.accessedThroughSharedURL) {
|
||||
downloadedURL =
|
||||
await PublicCollectionDownloadManager.getFile(
|
||||
|
|
|
@ -179,7 +179,8 @@ function PhotoViewer(props: Iprops) {
|
|||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const item = items[photoSwipe?.getCurrentIndex()];
|
||||
if (item && item.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
|
||||
if (!item) return;
|
||||
if (item.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
|
||||
const getVideoAndImage = () => {
|
||||
const video = document.getElementById(
|
||||
`live-photo-video-${item.id}`
|
||||
|
@ -213,32 +214,25 @@ function PhotoViewer(props: Iprops) {
|
|||
loading: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const downloadLivePhotoBtn = document.getElementById(
|
||||
`download-btn-${item.id}`
|
||||
) as HTMLButtonElement;
|
||||
if (downloadLivePhotoBtn) {
|
||||
const downloadLivePhoto = () => {
|
||||
const downloadFile = () => {
|
||||
downloadFileHelper(photoSwipe.currItem);
|
||||
};
|
||||
|
||||
downloadLivePhotoBtn.addEventListener(
|
||||
'click',
|
||||
downloadLivePhoto
|
||||
);
|
||||
return () => {
|
||||
downloadLivePhotoBtn.removeEventListener(
|
||||
'click',
|
||||
downloadLivePhoto
|
||||
);
|
||||
setLivePhotoBtnOptions(defaultLivePhotoDefaultOptions);
|
||||
};
|
||||
if (downloadLivePhotoBtn) {
|
||||
downloadLivePhotoBtn.addEventListener('click', downloadFile);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (downloadLivePhotoBtn) {
|
||||
downloadLivePhotoBtn.removeEventListener('click', downloadFile);
|
||||
}
|
||||
setLivePhotoBtnOptions(defaultLivePhotoDefaultOptions);
|
||||
};
|
||||
}
|
||||
}, [photoSwipe?.currItem, isOpen, isSourceLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -13,13 +13,15 @@ export class ElectronFFmpeg implements IFFmpeg {
|
|||
async run(
|
||||
cmd: string[],
|
||||
inputFile: ElectronFile | File,
|
||||
outputFilename: string
|
||||
outputFilename: string,
|
||||
dontTimeout?: boolean
|
||||
) {
|
||||
if (this.electronAPIs?.runFFmpegCmd) {
|
||||
return this.electronAPIs.runFFmpegCmd(
|
||||
cmd,
|
||||
inputFile,
|
||||
outputFilename
|
||||
outputFilename,
|
||||
dontTimeout
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ export interface IFFmpeg {
|
|||
run: (
|
||||
cmd: string[],
|
||||
inputFile: File | ElectronFile,
|
||||
outputFilename: string
|
||||
outputFilename: string,
|
||||
dontTimeout?: boolean
|
||||
) => Promise<File | ElectronFile>;
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,8 @@ export async function convertToMP4(file: File | ElectronFile) {
|
|||
OUTPUT_PATH_PLACEHOLDER,
|
||||
],
|
||||
file,
|
||||
'output.mp4'
|
||||
'output.mp4',
|
||||
true
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, 'ffmpeg convertToMP4 failed');
|
||||
|
|
|
@ -55,10 +55,16 @@ export async function updateCreationTimeWithExif(
|
|||
correctCreationTime = getUnixTimeInMicroSeconds(
|
||||
exifData?.DateTimeOriginal ?? exifData?.DateCreated
|
||||
);
|
||||
} else {
|
||||
} else if (fixOption === FIX_OPTIONS.DATE_TIME_DIGITIZED) {
|
||||
correctCreationTime = getUnixTimeInMicroSeconds(
|
||||
exifData?.CreateDate
|
||||
);
|
||||
} else if (fixOption === FIX_OPTIONS.METADATA_DATE) {
|
||||
correctCreationTime = getUnixTimeInMicroSeconds(
|
||||
exifData?.MetadataDate
|
||||
);
|
||||
} else {
|
||||
throw new Error('Invalid fix option');
|
||||
}
|
||||
}
|
||||
if (
|
||||
|
|
|
@ -15,6 +15,7 @@ type ParsedEXIFData = Record<string, any> &
|
|||
CreateDate: Date;
|
||||
ModifyDate: Date;
|
||||
DateCreated: Date;
|
||||
MetadataDate: Date;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
imageWidth: number;
|
||||
|
@ -27,6 +28,7 @@ type RawEXIFData = Record<string, any> &
|
|||
CreateDate: string;
|
||||
ModifyDate: string;
|
||||
DateCreated: string;
|
||||
MetadataDate: string;
|
||||
GPSLatitude: number[];
|
||||
GPSLongitude: number[];
|
||||
GPSLatitudeRef: string;
|
||||
|
@ -91,6 +93,7 @@ function parseExifData(exifData: RawEXIFData): ParsedEXIFData {
|
|||
ExifImageWidth,
|
||||
PixelXDimension,
|
||||
PixelYDimension,
|
||||
MetadataDate,
|
||||
...rest
|
||||
} = exifData;
|
||||
const parsedExif: ParsedEXIFData = { ...rest };
|
||||
|
@ -106,6 +109,9 @@ function parseExifData(exifData: RawEXIFData): ParsedEXIFData {
|
|||
if (DateCreated) {
|
||||
parsedExif.DateCreated = parseEXIFDate(exifData.DateCreated);
|
||||
}
|
||||
if (MetadataDate) {
|
||||
parsedExif.MetadataDate = parseEXIFDate(exifData.MetadataDate);
|
||||
}
|
||||
if (exifData.GPSLatitude && exifData.GPSLongitude) {
|
||||
const parsedLocation = parseEXIFLocation(
|
||||
exifData.GPSLatitude,
|
||||
|
@ -294,6 +300,7 @@ export function getEXIFTime(exifData: ParsedEXIFData): number {
|
|||
exifData.DateTimeOriginal ??
|
||||
exifData.DateCreated ??
|
||||
exifData.CreateDate ??
|
||||
exifData.MetadataDate ??
|
||||
exifData.ModifyDate;
|
||||
if (!dateTime) {
|
||||
return null;
|
||||
|
|
|
@ -48,6 +48,7 @@ const EXIF_TAGS_NEEDED = [
|
|||
'ImageHeight',
|
||||
'PixelXDimension',
|
||||
'PixelYDimension',
|
||||
'MetadataDate',
|
||||
];
|
||||
|
||||
export async function extractMetadata(
|
||||
|
|
|
@ -32,13 +32,22 @@ export class WasmFFmpeg {
|
|||
}
|
||||
}
|
||||
|
||||
async run(cmd: string[], inputFile: File, outputFileName: string) {
|
||||
const response = this.ffmpegTaskQueue.queueUpRequest(() =>
|
||||
promiseWithTimeout<File>(
|
||||
async run(
|
||||
cmd: string[],
|
||||
inputFile: File,
|
||||
outputFileName: string,
|
||||
dontTimeout = false
|
||||
) {
|
||||
const response = this.ffmpegTaskQueue.queueUpRequest(() => {
|
||||
if (dontTimeout) {
|
||||
return this.execute(cmd, inputFile, outputFileName);
|
||||
} else {
|
||||
return promiseWithTimeout<File>(
|
||||
this.execute(cmd, inputFile, outputFileName),
|
||||
FFMPEG_EXECUTION_WAIT_TIME
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
try {
|
||||
return await response.promise;
|
||||
} catch (e) {
|
||||
|
|
|
@ -74,7 +74,8 @@ export interface ElectronAPIs {
|
|||
runFFmpegCmd: (
|
||||
cmd: string[],
|
||||
inputFile: File | ElectronFile,
|
||||
outputFileName: string
|
||||
outputFileName: string,
|
||||
dontTimeout?: boolean
|
||||
) => Promise<File>;
|
||||
muteUpdateNotification: (version: string) => void;
|
||||
generateImageThumbnail: (
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
updateFileMagicMetadata,
|
||||
updateFilePublicMagicMetadata,
|
||||
} from 'services/fileService';
|
||||
import { isPlaybackPossible } from 'utils/photoFrame';
|
||||
|
||||
const WAIT_TIME_IMAGE_CONVERSION = 30 * 1000;
|
||||
|
||||
|
@ -293,6 +294,16 @@ export async function getRenderableFileURL(file: EnteFile, fileBlob: Blob) {
|
|||
original: [URL.createObjectURL(fileBlob)],
|
||||
};
|
||||
}
|
||||
case FILE_TYPE.VIDEO: {
|
||||
const convertedBlob = await getPlayableVideo(
|
||||
file.metadata.title,
|
||||
new Uint8Array(await fileBlob.arrayBuffer())
|
||||
);
|
||||
return {
|
||||
converted: [URL.createObjectURL(convertedBlob)],
|
||||
original: [URL.createObjectURL(fileBlob)],
|
||||
};
|
||||
}
|
||||
default: {
|
||||
const previewURL = await createTypedObjectURL(
|
||||
fileBlob,
|
||||
|
@ -318,12 +329,28 @@ async function getRenderableLivePhoto(
|
|||
]);
|
||||
}
|
||||
|
||||
async function getPlayableVideo(videoNameTitle: string, video: Uint8Array) {
|
||||
export async function getPlayableVideo(
|
||||
videoNameTitle: string,
|
||||
video: Uint8Array
|
||||
) {
|
||||
try {
|
||||
const isPlayable = await isPlaybackPossible(
|
||||
URL.createObjectURL(new Blob([video]))
|
||||
);
|
||||
if (isPlayable) {
|
||||
return new Blob([video.buffer]);
|
||||
} else {
|
||||
addLogLine('video format not supported, converting it');
|
||||
const mp4ConvertedVideo = await ffmpegService.convertToMP4(
|
||||
new File([video], videoNameTitle)
|
||||
);
|
||||
return new Blob([await mp4ConvertedVideo.arrayBuffer()]);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, 'video conversion failed');
|
||||
return new Blob([video.buffer]);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRenderableImage(fileName: string, imageBlob: Blob) {
|
||||
if (await isFileHEIC(imageBlob, fileName)) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { FILE_TYPE } from 'constants/file';
|
||||
import { t } from 'i18next';
|
||||
import { EnteFile } from 'types/file';
|
||||
import { MergedSourceURL } from 'types/gallery';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { t } from 'i18next';
|
||||
|
||||
const WAIT_FOR_VIDEO_PLAYBACK = 1 * 1000;
|
||||
|
||||
|
@ -119,9 +119,9 @@ export async function updateFileSrcProps(
|
|||
<img src="${file.msrc}" onContextMenu="return false;"/>
|
||||
<div class="download-banner">
|
||||
${t('VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD')}
|
||||
<a class="btn btn-outline-success" href=${convertedVideoURL} download="${
|
||||
file.metadata.title
|
||||
}"">Download</a>
|
||||
<button class = "btn btn-outline-success" id = "download-btn-${
|
||||
file.id
|
||||
}">${t('DOWNLOAD')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -7,8 +7,8 @@ export class DedicatedFFmpegWorker {
|
|||
this.wasmFFmpeg = new WasmFFmpeg();
|
||||
}
|
||||
|
||||
run(cmd, inputFile, outputFileName) {
|
||||
return this.wasmFFmpeg.run(cmd, inputFile, outputFileName);
|
||||
run(cmd, inputFile, outputFileName, dontTimeout) {
|
||||
return this.wasmFFmpeg.run(cmd, inputFile, outputFileName, dontTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue