immich/server/src/microservices/processors/metadata-extraction.processor.ts

476 lines
16 KiB
TypeScript
Raw Normal View History

import {
IAlbumRepository,
IAssetRepository,
IBaseJob,
ICryptoRepository,
IEntityJob,
IGeocodingRepository,
IJobRepository,
IStorageRepository,
JobName,
JOBS_ASSET_PAGINATION_SIZE,
QueueName,
StorageCore,
StorageFolder,
usePagination,
WithoutProperty,
} from '@app/domain';
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
import { Inject, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import tz_lookup from '@photostructure/tz-lookup';
import { exiftool, Tags } from 'exiftool-vendored';
2023-03-03 17:49:22 -05:00
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg';
import { Duration } from 'luxon';
import fs from 'node:fs/promises';
import path from 'node:path';
import sharp from 'sharp';
2023-03-03 17:49:22 -05:00
import { promisify } from 'util';
import { parseLatitude, parseLongitude } from '../utils/exif/coordinates';
import { exifTimeZone, exifToDate } from '../utils/exif/date-time';
import { parseISO } from '../utils/exif/iso';
import { toNumberOrNull } from '../utils/numbers';
2023-03-03 17:49:22 -05:00
const ffprobe = promisify<string, FfprobeData>(ffmpeg.ffprobe);
interface MotionPhotosData {
isMotionPhoto: string | number | null;
isMicroVideo: string | number | null;
videoOffset: string | number | null;
directory: DirectoryEntry[] | null;
}
interface DirectoryItem {
Length?: number;
Mime: string;
Padding?: number;
Semantic?: string;
}
interface DirectoryEntry {
Item: DirectoryItem;
}
interface ImmichTags extends Tags {
ContentIdentifier?: string;
MotionPhoto?: number;
MotionPhotoVersion?: number;
MotionPhotoPresentationTimestampUs?: number;
}
WIP refactor container and queuing system (#206) * refactor microservices to machine-learning * Update tGithub issue template with correct task syntax * Added microservices container * Communicate between service based on queue system * added dependency * Fixed problem with having to import BullQueue into the individual service * Added todo * refactor server into monorepo with microservices * refactor database and entity to library * added simple migration * Move migrations and database config to library * Migration works in library * Cosmetic change in logging message * added user dto * Fixed issue with testing not able to find the shared library * Clean up library mapping path * Added webp generator to microservices * Update Github Action build latest * Fixed issue NPM cannot install due to conflict witl Bull Queue * format project with prettier * Modified docker-compose file * Add GH Action for Staging build: * Fixed GH action job name * Modified GH Action to only build & push latest when pushing to main * Added Test 2e2 Github Action * Added Test 2e2 Github Action * Implemented microservice to extract exif * Added cronjob to scan and generate webp thumbnail at midnight * Refactor to ireduce hit time to database when running microservices * Added error handling to asset services that handle read file from disk * Added video transcoding queue to process one video at a time * Fixed loading spinner on web while loading covering the info panel * Add mechanism to show new release announcement to web and mobile app (#209) * Added changelog page * Fixed issues based on PR comments * Fixed issue with video transcoding run on the server * Change entry point content for backward combatibility when starting up server * Added announcement box * Added error handling to failed silently when the app version checking is not able to make the request to GITHUB * Added new version announcement overlay * Update message * Added messages * Added logic to check and show announcement * Add method to handle saving new version * Added button to dimiss the acknowledge message * Up version for deployment to the app store
2022-06-11 16:12:06 -05:00
export class MetadataExtractionProcessor {
2023-01-13 09:23:12 -05:00
private logger = new Logger(MetadataExtractionProcessor.name);
private reverseGeocodingEnabled: boolean;
private storageCore = new StorageCore();
WIP refactor container and queuing system (#206) * refactor microservices to machine-learning * Update tGithub issue template with correct task syntax * Added microservices container * Communicate between service based on queue system * added dependency * Fixed problem with having to import BullQueue into the individual service * Added todo * refactor server into monorepo with microservices * refactor database and entity to library * added simple migration * Move migrations and database config to library * Migration works in library * Cosmetic change in logging message * added user dto * Fixed issue with testing not able to find the shared library * Clean up library mapping path * Added webp generator to microservices * Update Github Action build latest * Fixed issue NPM cannot install due to conflict witl Bull Queue * format project with prettier * Modified docker-compose file * Add GH Action for Staging build: * Fixed GH action job name * Modified GH Action to only build & push latest when pushing to main * Added Test 2e2 Github Action * Added Test 2e2 Github Action * Implemented microservice to extract exif * Added cronjob to scan and generate webp thumbnail at midnight * Refactor to ireduce hit time to database when running microservices * Added error handling to asset services that handle read file from disk * Added video transcoding queue to process one video at a time * Fixed loading spinner on web while loading covering the info panel * Add mechanism to show new release announcement to web and mobile app (#209) * Added changelog page * Fixed issues based on PR comments * Fixed issue with video transcoding run on the server * Change entry point content for backward combatibility when starting up server * Added announcement box * Added error handling to failed silently when the app version checking is not able to make the request to GITHUB * Added new version announcement overlay * Update message * Added messages * Added logic to check and show announcement * Add method to handle saving new version * Added button to dimiss the acknowledge message * Up version for deployment to the app store
2022-06-11 16:12:06 -05:00
constructor(
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
@Inject(IJobRepository) private jobRepository: IJobRepository,
@Inject(IGeocodingRepository) private geocodingRepository: IGeocodingRepository,
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
WIP refactor container and queuing system (#206) * refactor microservices to machine-learning * Update tGithub issue template with correct task syntax * Added microservices container * Communicate between service based on queue system * added dependency * Fixed problem with having to import BullQueue into the individual service * Added todo * refactor server into monorepo with microservices * refactor database and entity to library * added simple migration * Move migrations and database config to library * Migration works in library * Cosmetic change in logging message * added user dto * Fixed issue with testing not able to find the shared library * Clean up library mapping path * Added webp generator to microservices * Update Github Action build latest * Fixed issue NPM cannot install due to conflict witl Bull Queue * format project with prettier * Modified docker-compose file * Add GH Action for Staging build: * Fixed GH action job name * Modified GH Action to only build & push latest when pushing to main * Added Test 2e2 Github Action * Added Test 2e2 Github Action * Implemented microservice to extract exif * Added cronjob to scan and generate webp thumbnail at midnight * Refactor to ireduce hit time to database when running microservices * Added error handling to asset services that handle read file from disk * Added video transcoding queue to process one video at a time * Fixed loading spinner on web while loading covering the info panel * Add mechanism to show new release announcement to web and mobile app (#209) * Added changelog page * Fixed issues based on PR comments * Fixed issue with video transcoding run on the server * Change entry point content for backward combatibility when starting up server * Added announcement box * Added error handling to failed silently when the app version checking is not able to make the request to GITHUB * Added new version announcement overlay * Update message * Added messages * Added logic to check and show announcement * Add method to handle saving new version * Added button to dimiss the acknowledge message * Up version for deployment to the app store
2022-06-11 16:12:06 -05:00
2023-01-13 09:23:12 -05:00
configService: ConfigService,
WIP refactor container and queuing system (#206) * refactor microservices to machine-learning * Update tGithub issue template with correct task syntax * Added microservices container * Communicate between service based on queue system * added dependency * Fixed problem with having to import BullQueue into the individual service * Added todo * refactor server into monorepo with microservices * refactor database and entity to library * added simple migration * Move migrations and database config to library * Migration works in library * Cosmetic change in logging message * added user dto * Fixed issue with testing not able to find the shared library * Clean up library mapping path * Added webp generator to microservices * Update Github Action build latest * Fixed issue NPM cannot install due to conflict witl Bull Queue * format project with prettier * Modified docker-compose file * Add GH Action for Staging build: * Fixed GH action job name * Modified GH Action to only build & push latest when pushing to main * Added Test 2e2 Github Action * Added Test 2e2 Github Action * Implemented microservice to extract exif * Added cronjob to scan and generate webp thumbnail at midnight * Refactor to ireduce hit time to database when running microservices * Added error handling to asset services that handle read file from disk * Added video transcoding queue to process one video at a time * Fixed loading spinner on web while loading covering the info panel * Add mechanism to show new release announcement to web and mobile app (#209) * Added changelog page * Fixed issues based on PR comments * Fixed issue with video transcoding run on the server * Change entry point content for backward combatibility when starting up server * Added announcement box * Added error handling to failed silently when the app version checking is not able to make the request to GITHUB * Added new version announcement overlay * Update message * Added messages * Added logic to check and show announcement * Add method to handle saving new version * Added button to dimiss the acknowledge message * Up version for deployment to the app store
2022-06-11 16:12:06 -05:00
) {
this.reverseGeocodingEnabled = !configService.get('DISABLE_REVERSE_GEOCODING');
WIP refactor container and queuing system (#206) * refactor microservices to machine-learning * Update tGithub issue template with correct task syntax * Added microservices container * Communicate between service based on queue system * added dependency * Fixed problem with having to import BullQueue into the individual service * Added todo * refactor server into monorepo with microservices * refactor database and entity to library * added simple migration * Move migrations and database config to library * Migration works in library * Cosmetic change in logging message * added user dto * Fixed issue with testing not able to find the shared library * Clean up library mapping path * Added webp generator to microservices * Update Github Action build latest * Fixed issue NPM cannot install due to conflict witl Bull Queue * format project with prettier * Modified docker-compose file * Add GH Action for Staging build: * Fixed GH action job name * Modified GH Action to only build & push latest when pushing to main * Added Test 2e2 Github Action * Added Test 2e2 Github Action * Implemented microservice to extract exif * Added cronjob to scan and generate webp thumbnail at midnight * Refactor to ireduce hit time to database when running microservices * Added error handling to asset services that handle read file from disk * Added video transcoding queue to process one video at a time * Fixed loading spinner on web while loading covering the info panel * Add mechanism to show new release announcement to web and mobile app (#209) * Added changelog page * Fixed issues based on PR comments * Fixed issue with video transcoding run on the server * Change entry point content for backward combatibility when starting up server * Added announcement box * Added error handling to failed silently when the app version checking is not able to make the request to GITHUB * Added new version announcement overlay * Update message * Added messages * Added logic to check and show announcement * Add method to handle saving new version * Added button to dimiss the acknowledge message * Up version for deployment to the app store
2022-06-11 16:12:06 -05:00
}
async init(deleteCache = false) {
this.logger.log(`Reverse geocoding is ${this.reverseGeocodingEnabled ? 'enabled' : 'disabled'}`);
if (!this.reverseGeocodingEnabled) {
return;
2022-10-07 09:15:05 -05:00
}
try {
if (deleteCache) {
await this.geocodingRepository.deleteCache();
}
this.logger.log('Initializing Reverse Geocoding');
2022-10-07 09:15:05 -05:00
await this.jobRepository.pause(QueueName.METADATA_EXTRACTION);
await this.geocodingRepository.init();
await this.jobRepository.resume(QueueName.METADATA_EXTRACTION);
this.logger.log('Reverse Geocoding Initialized');
} catch (error: any) {
this.logger.error(`Unable to initialize reverse geocoding: ${error}`, error?.stack);
}
}
async handleLivePhotoLinking(job: IEntityJob) {
const { id } = job;
const [asset] = await this.assetRepository.getByIds([id]);
if (!asset?.exifInfo) {
return false;
}
if (!asset.exifInfo.livePhotoCID) {
return true;
}
const otherType = asset.type === AssetType.VIDEO ? AssetType.IMAGE : AssetType.VIDEO;
const match = await this.assetRepository.findLivePhotoMatch({
livePhotoCID: asset.exifInfo.livePhotoCID,
ownerId: asset.ownerId,
otherAssetId: asset.id,
type: otherType,
});
if (!match) {
return true;
}
const [photoAsset, motionAsset] = asset.type === AssetType.IMAGE ? [asset, match] : [match, asset];
await this.assetRepository.save({ id: photoAsset.id, livePhotoVideoId: motionAsset.id });
await this.assetRepository.save({ id: motionAsset.id, isVisible: false });
await this.albumRepository.removeAsset(motionAsset.id);
return true;
}
async handleQueueMetadataExtraction(job: IBaseJob) {
const { force } = job;
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => {
return force
? this.assetRepository.getAll(pagination)
: this.assetRepository.getWithout(pagination, WithoutProperty.EXIF);
});
for await (const assets of assetPagination) {
for (const asset of assets) {
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: asset.id } });
}
}
return true;
}
async handleMetadataExtraction({ id }: IEntityJob) {
const [asset] = await this.assetRepository.getByIds([id]);
if (!asset || !asset.isVisible) {
return false;
}
if (asset.type === AssetType.VIDEO) {
return this.handleVideoMetadataExtraction(asset);
} else {
return this.handlePhotoMetadataExtraction(asset);
}
}
private async handlePhotoMetadataExtraction(asset: AssetEntity) {
const mediaExifData = await exiftool.read<ImmichTags>(asset.originalPath).catch((error: any) => {
this.logger.warn(
`The exifData parsing failed due to ${error} for asset ${asset.id} at ${asset.originalPath}`,
error?.stack,
);
return null;
});
const sidecarExifData = asset.sidecarPath
? await exiftool.read<ImmichTags>(asset.sidecarPath).catch((error: any) => {
this.logger.warn(
`The exifData parsing failed due to ${error} for asset ${asset.id} at ${asset.originalPath}`,
error?.stack,
);
return null;
})
: {};
const getExifProperty = <T extends keyof ImmichTags>(
...properties: T[]
): NonNullable<ImmichTags[T]> | string | null => {
for (const property of properties) {
const value = sidecarExifData?.[property] ?? mediaExifData?.[property];
if (value !== null && value !== undefined) {
// Can also be string when the value cannot be parsed
return value;
}
}
return null;
};
const timeZone = exifTimeZone(getExifProperty('DateTimeOriginal', 'CreateDate') ?? asset.fileCreatedAt);
const fileCreatedAt = exifToDate(getExifProperty('DateTimeOriginal', 'CreateDate') ?? asset.fileCreatedAt);
const fileModifiedAt = exifToDate(getExifProperty('ModifyDate') ?? asset.fileModifiedAt);
const fileStats = await fs.stat(asset.originalPath);
const fileSizeInBytes = fileStats.size;
const newExif = new ExifEntity();
newExif.assetId = asset.id;
newExif.fileSizeInByte = fileSizeInBytes;
newExif.make = getExifProperty('Make');
newExif.model = getExifProperty('Model');
newExif.exifImageHeight = toNumberOrNull(getExifProperty('ExifImageHeight', 'ImageHeight'));
newExif.exifImageWidth = toNumberOrNull(getExifProperty('ExifImageWidth', 'ImageWidth'));
newExif.exposureTime = getExifProperty('ExposureTime');
newExif.orientation = getExifProperty('Orientation')?.toString() ?? null;
newExif.dateTimeOriginal = fileCreatedAt;
newExif.modifyDate = fileModifiedAt;
newExif.timeZone = timeZone;
newExif.lensModel = getExifProperty('LensModel');
newExif.fNumber = toNumberOrNull(getExifProperty('FNumber'));
newExif.focalLength = toNumberOrNull(getExifProperty('FocalLength'));
// Handle array values by converting to string
const iso = getExifProperty('ISO')?.toString();
newExif.iso = iso ? parseISO(iso) : null;
const latitude = getExifProperty('GPSLatitude');
const longitude = getExifProperty('GPSLongitude');
const lat = parseLatitude(latitude);
const lon = parseLongitude(longitude);
if (lat === 0 && lon === 0) {
this.logger.warn(`Latitude & Longitude were on Null Island (${lat},${lon}), not assigning coordinates`);
} else {
newExif.latitude = lat;
newExif.longitude = lon;
}
feat (web/server) 360 degrees Web panoramas [attempt 2] (#3412) * commit 1 (isPanorama: boolean) * working solution for projectiontypeenum * fix * format fix * fix * fix * fix * fix * enum projectiontype * working solution with exif * fix * reverted > * fix format * reverted auto-magic api.ts prettification * fix * reverted api.ts autogenerated * api ts regenerated * Update web/src/lib/components/assets/thumbnail/thumbnail.svelte Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com> * Update web/src/lib/components/asset-viewer/asset-viewer.svelte Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com> * exifProjectionType * Update server/src/microservices/processors/metadata-extraction.processor.ts Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com> * projectionType?: string = ProjectionType.NONE; * not null * projectionType!: ProjectionType; * opeapi generator fix * fixes * fix * fix * generate api * asset.exifInifo?.projectionType * Update server/src/domain/asset/response-dto/exif-response.dto.ts Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> * Update server/src/microservices/processors/metadata-extraction.processor.ts Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> * enum -> varchar;projectiontypeenum->projectiontype * asset-viewer fixed prettiffier * @Column({}) single line * enum | string * make api * enum | string * enum | str fix * fix * chore: use string instead of enum * chore: open api * fix: checks --------- Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com> Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-07-28 06:29:09 +02:00
const projectionType = getExifProperty('ProjectionType');
if (projectionType) {
newExif.projectionType = String(projectionType).toUpperCase();
}
newExif.livePhotoCID = getExifProperty('MediaGroupUUID');
const rawDirectory = getExifProperty('Directory');
await this.applyMotionPhotos(asset, {
isMotionPhoto: getExifProperty('MotionPhoto'),
isMicroVideo: getExifProperty('MicroVideo'),
videoOffset: getExifProperty('MicroVideoOffset'),
directory: Array.isArray(rawDirectory) ? (rawDirectory as DirectoryEntry[]) : null,
});
await this.applyReverseGeocoding(asset, newExif);
/**
* IF the EXIF doesn't contain the width and height of the image,
* We will use Sharpjs to get the information.
*/
if (!newExif.exifImageHeight || !newExif.exifImageWidth || !newExif.orientation) {
const metadata = await sharp(asset.originalPath).metadata();
if (newExif.exifImageHeight === null) {
newExif.exifImageHeight = metadata.height || null;
}
if (newExif.exifImageWidth === null) {
newExif.exifImageWidth = metadata.width || null;
}
if (newExif.orientation === null) {
newExif.orientation = metadata.orientation !== undefined ? `${metadata.orientation}` : null;
}
WIP refactor container and queuing system (#206) * refactor microservices to machine-learning * Update tGithub issue template with correct task syntax * Added microservices container * Communicate between service based on queue system * added dependency * Fixed problem with having to import BullQueue into the individual service * Added todo * refactor server into monorepo with microservices * refactor database and entity to library * added simple migration * Move migrations and database config to library * Migration works in library * Cosmetic change in logging message * added user dto * Fixed issue with testing not able to find the shared library * Clean up library mapping path * Added webp generator to microservices * Update Github Action build latest * Fixed issue NPM cannot install due to conflict witl Bull Queue * format project with prettier * Modified docker-compose file * Add GH Action for Staging build: * Fixed GH action job name * Modified GH Action to only build & push latest when pushing to main * Added Test 2e2 Github Action * Added Test 2e2 Github Action * Implemented microservice to extract exif * Added cronjob to scan and generate webp thumbnail at midnight * Refactor to ireduce hit time to database when running microservices * Added error handling to asset services that handle read file from disk * Added video transcoding queue to process one video at a time * Fixed loading spinner on web while loading covering the info panel * Add mechanism to show new release announcement to web and mobile app (#209) * Added changelog page * Fixed issues based on PR comments * Fixed issue with video transcoding run on the server * Change entry point content for backward combatibility when starting up server * Added announcement box * Added error handling to failed silently when the app version checking is not able to make the request to GITHUB * Added new version announcement overlay * Update message * Added messages * Added logic to check and show announcement * Add method to handle saving new version * Added button to dimiss the acknowledge message * Up version for deployment to the app store
2022-06-11 16:12:06 -05:00
}
await this.assetRepository.upsertExif(newExif);
await this.assetRepository.save({
id: asset.id,
fileCreatedAt: fileCreatedAt || undefined,
updatedAt: new Date(),
});
return true;
}
private async handleVideoMetadataExtraction(asset: AssetEntity) {
const data = await ffprobe(asset.originalPath);
const durationString = this.extractDuration(data.format.duration || asset.duration);
let fileCreatedAt = asset.fileCreatedAt;
const videoTags = data.format.tags;
if (videoTags) {
if (videoTags['com.apple.quicktime.creationdate']) {
fileCreatedAt = new Date(videoTags['com.apple.quicktime.creationdate']);
} else if (videoTags['creation_time']) {
fileCreatedAt = new Date(videoTags['creation_time']);
}
}
const exifData = await exiftool.read<ImmichTags>(asset.sidecarPath || asset.originalPath).catch((error: any) => {
this.logger.warn(
`The exifData parsing failed due to ${error} for asset ${asset.id} at ${asset.originalPath}`,
error?.stack,
);
return null;
});
const newExif = new ExifEntity();
newExif.assetId = asset.id;
newExif.fileSizeInByte = data.format.size || null;
newExif.dateTimeOriginal = fileCreatedAt ? new Date(fileCreatedAt) : null;
newExif.modifyDate = null;
newExif.timeZone = null;
newExif.latitude = null;
newExif.longitude = null;
newExif.city = null;
newExif.state = null;
newExif.country = null;
newExif.fps = null;
newExif.livePhotoCID = exifData?.ContentIdentifier || null;
if (videoTags && videoTags['location']) {
const location = videoTags['location'] as string;
const locationRegex = /([+-][0-9]+\.[0-9]+)([+-][0-9]+\.[0-9]+)\/$/;
const match = location.match(locationRegex);
if (match?.length === 3) {
newExif.latitude = parseLatitude(match[1]);
newExif.longitude = parseLongitude(match[2]);
}
} else if (videoTags && videoTags['com.apple.quicktime.location.ISO6709']) {
const location = videoTags['com.apple.quicktime.location.ISO6709'] as string;
const locationRegex = /([+-][0-9]+\.[0-9]+)([+-][0-9]+\.[0-9]+)([+-][0-9]+\.[0-9]+)\/$/;
const match = location.match(locationRegex);
if (match?.length === 4) {
newExif.latitude = parseLatitude(match[1]);
newExif.longitude = parseLongitude(match[2]);
}
}
if (newExif.longitude && newExif.latitude) {
try {
newExif.timeZone = tz_lookup(newExif.latitude, newExif.longitude);
} catch (error: any) {
this.logger.warn(`Error while calculating timezone from gps coordinates: ${error}`, error?.stack);
}
}
await this.applyReverseGeocoding(asset, newExif);
for (const stream of data.streams) {
if (stream.codec_type === 'video') {
newExif.exifImageWidth = stream.width || null;
newExif.exifImageHeight = stream.height || null;
if (typeof stream.rotation === 'string') {
newExif.orientation = stream.rotation;
} else if (typeof stream.rotation === 'number') {
newExif.orientation = `${stream.rotation}`;
} else {
newExif.orientation = null;
}
if (stream.r_frame_rate) {
const fpsParts = stream.r_frame_rate.split('/');
if (fpsParts.length === 2) {
newExif.fps = Math.round(parseInt(fpsParts[0]) / parseInt(fpsParts[1]));
}
}
}
}
await this.assetRepository.upsertExif(newExif);
await this.assetRepository.save({ id: asset.id, duration: durationString, fileCreatedAt });
return true;
}
private async applyReverseGeocoding(asset: AssetEntity, newExif: ExifEntity) {
const { latitude, longitude } = newExif;
if (this.reverseGeocodingEnabled && longitude && latitude) {
try {
const { country, state, city } = await this.geocodingRepository.reverseGeocode({ latitude, longitude });
newExif.country = country;
newExif.state = state;
newExif.city = city;
} catch (error: any) {
this.logger.warn(
`Unable to run reverse geocoding due to ${error} for asset ${asset.id} at ${asset.originalPath}`,
error?.stack,
);
}
}
}
private async applyMotionPhotos(asset: AssetEntity, data: MotionPhotosData) {
if (asset.livePhotoVideoId) {
return;
}
const { isMotionPhoto, isMicroVideo, directory, videoOffset } = data;
let length = 0;
let padding = 0;
if (isMotionPhoto && directory) {
for (const entry of directory) {
if (entry.Item.Semantic == 'MotionPhoto') {
length = entry.Item.Length ?? 0;
padding = entry.Item.Padding ?? 0;
break;
}
}
}
if (isMicroVideo && typeof videoOffset === 'number') {
length = videoOffset;
}
if (!length) {
return;
}
this.logger.debug(`Starting motion photo video extraction (${asset.id})`);
let file = null;
try {
const encodedFolder = this.storageCore.getFolderLocation(StorageFolder.ENCODED_VIDEO, asset.ownerId);
const encodedFile = path.join(encodedFolder, path.parse(asset.originalPath).name + '.mp4');
this.storageRepository.mkdirSync(encodedFolder);
file = await fs.open(asset.originalPath);
const stat = await file.stat();
const position = stat.size - length - padding;
const video = await file.read({ buffer: Buffer.alloc(length), position, length });
const checksum = await this.cryptoRepository.hashSha1(video.buffer);
let motionAsset = await this.assetRepository.getByChecksum(asset.ownerId, checksum);
if (!motionAsset) {
motionAsset = await this.assetRepository.save({
type: AssetType.VIDEO,
fileCreatedAt: asset.fileCreatedAt ?? asset.createdAt,
fileModifiedAt: asset.fileModifiedAt,
checksum,
ownerId: asset.ownerId,
originalPath: encodedFile,
originalFileName: asset.originalFileName,
isVisible: false,
isReadOnly: true,
deviceAssetId: 'NONE',
deviceId: 'NONE',
});
await fs.writeFile(encodedFile, video.buffer);
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: motionAsset.id } });
}
await this.assetRepository.save({ id: asset.id, livePhotoVideoId: motionAsset.id });
this.logger.debug(`Finished motion photo video extraction (${asset.id})`);
} catch (error: Error | any) {
this.logger.error(`Failed to extract live photo ${asset.originalPath}: ${error}`, error?.stack);
} finally {
await file?.close();
}
}
2023-03-03 17:49:22 -05:00
private extractDuration(duration: number | string | null) {
const videoDurationInSecond = Number(duration);
if (!videoDurationInSecond) {
return null;
}
return Duration.fromObject({ seconds: videoDurationInSecond }).toFormat('hh:mm:ss.SSS');
}
WIP refactor container and queuing system (#206) * refactor microservices to machine-learning * Update tGithub issue template with correct task syntax * Added microservices container * Communicate between service based on queue system * added dependency * Fixed problem with having to import BullQueue into the individual service * Added todo * refactor server into monorepo with microservices * refactor database and entity to library * added simple migration * Move migrations and database config to library * Migration works in library * Cosmetic change in logging message * added user dto * Fixed issue with testing not able to find the shared library * Clean up library mapping path * Added webp generator to microservices * Update Github Action build latest * Fixed issue NPM cannot install due to conflict witl Bull Queue * format project with prettier * Modified docker-compose file * Add GH Action for Staging build: * Fixed GH action job name * Modified GH Action to only build & push latest when pushing to main * Added Test 2e2 Github Action * Added Test 2e2 Github Action * Implemented microservice to extract exif * Added cronjob to scan and generate webp thumbnail at midnight * Refactor to ireduce hit time to database when running microservices * Added error handling to asset services that handle read file from disk * Added video transcoding queue to process one video at a time * Fixed loading spinner on web while loading covering the info panel * Add mechanism to show new release announcement to web and mobile app (#209) * Added changelog page * Fixed issues based on PR comments * Fixed issue with video transcoding run on the server * Change entry point content for backward combatibility when starting up server * Added announcement box * Added error handling to failed silently when the app version checking is not able to make the request to GITHUB * Added new version announcement overlay * Update message * Added messages * Added logic to check and show announcement * Add method to handle saving new version * Added button to dimiss the acknowledge message * Up version for deployment to the app store
2022-06-11 16:12:06 -05:00
}