|
@@ -85,9 +85,21 @@ export class MetadataExtractionProcessor {
|
|
|
|
|
|
const res: [] = geoCodeInfo.body['features'];
|
|
|
|
|
|
- const city = res.filter((geoInfo) => geoInfo['place_type'][0] == 'place')[0]['text'];
|
|
|
- const state = res.filter((geoInfo) => geoInfo['place_type'][0] == 'region')[0]['text'];
|
|
|
- const country = res.filter((geoInfo) => geoInfo['place_type'][0] == 'country')[0]['text'];
|
|
|
+ let city = '';
|
|
|
+ let state = '';
|
|
|
+ let country = '';
|
|
|
+
|
|
|
+ if (res.filter((geoInfo) => geoInfo['place_type'][0] == 'place')[0]) {
|
|
|
+ city = res.filter((geoInfo) => geoInfo['place_type'][0] == 'place')[0]['text'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.filter((geoInfo) => geoInfo['place_type'][0] == 'region')[0]) {
|
|
|
+ state = res.filter((geoInfo) => geoInfo['place_type'][0] == 'region')[0]['text'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.filter((geoInfo) => geoInfo['place_type'][0] == 'country')[0]) {
|
|
|
+ country = res.filter((geoInfo) => geoInfo['place_type'][0] == 'country')[0]['text'];
|
|
|
+ }
|
|
|
|
|
|
newExif.city = city || null;
|
|
|
newExif.state = state || null;
|
|
@@ -114,9 +126,21 @@ export class MetadataExtractionProcessor {
|
|
|
|
|
|
const res: [] = geoCodeInfo.body['features'];
|
|
|
|
|
|
- const city = res.filter((geoInfo) => geoInfo['place_type'][0] == 'place')[0]['text'];
|
|
|
- const state = res.filter((geoInfo) => geoInfo['place_type'][0] == 'region')[0]['text'];
|
|
|
- const country = res.filter((geoInfo) => geoInfo['place_type'][0] == 'country')[0]['text'];
|
|
|
+ let city = '';
|
|
|
+ let state = '';
|
|
|
+ let country = '';
|
|
|
+
|
|
|
+ if (res.filter((geoInfo) => geoInfo['place_type'][0] == 'place')[0]) {
|
|
|
+ city = res.filter((geoInfo) => geoInfo['place_type'][0] == 'place')[0]['text'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.filter((geoInfo) => geoInfo['place_type'][0] == 'region')[0]) {
|
|
|
+ state = res.filter((geoInfo) => geoInfo['place_type'][0] == 'region')[0]['text'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.filter((geoInfo) => geoInfo['place_type'][0] == 'country')[0]) {
|
|
|
+ country = res.filter((geoInfo) => geoInfo['place_type'][0] == 'country')[0]['text'];
|
|
|
+ }
|
|
|
|
|
|
await this.exifRepository.update({ id: exif.id }, { city, state, country });
|
|
|
}
|
|
@@ -168,31 +192,126 @@ export class MetadataExtractionProcessor {
|
|
|
async extractVideoMetadata(job: Job<IVideoLengthExtractionProcessor>) {
|
|
|
const { asset } = job.data;
|
|
|
|
|
|
- ffmpeg.ffprobe(asset.originalPath, async (err, data) => {
|
|
|
- if (!err) {
|
|
|
- let durationString = asset.duration;
|
|
|
- let createdAt = asset.createdAt;
|
|
|
+ try {
|
|
|
+ const data = await new Promise<ffmpeg.FfprobeData>((resolve, reject) =>
|
|
|
+ ffmpeg.ffprobe(asset.originalPath, (err, data) => {
|
|
|
+ if (err) return reject(err);
|
|
|
+ return resolve(data);
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ let durationString = asset.duration;
|
|
|
+ let createdAt = asset.createdAt;
|
|
|
+
|
|
|
+ if (data.format.duration) {
|
|
|
+ durationString = this.extractDuration(data.format.duration);
|
|
|
+ }
|
|
|
+
|
|
|
+ const videoTags = data.format.tags;
|
|
|
+ if (videoTags) {
|
|
|
+ if (videoTags['com.apple.quicktime.creationdate']) {
|
|
|
+ createdAt = String(videoTags['com.apple.quicktime.creationdate']);
|
|
|
+ } else if (videoTags['creation_time']) {
|
|
|
+ createdAt = String(videoTags['creation_time']);
|
|
|
+ } else {
|
|
|
+ createdAt = asset.createdAt;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ createdAt = asset.createdAt;
|
|
|
+ }
|
|
|
|
|
|
- if (data.format.duration) {
|
|
|
- durationString = this.extractDuration(data.format.duration);
|
|
|
+ const newExif = new ExifEntity();
|
|
|
+ newExif.assetId = asset.id;
|
|
|
+ newExif.description = '';
|
|
|
+ newExif.fileSizeInByte = data.format.size || null;
|
|
|
+ newExif.dateTimeOriginal = createdAt ? new Date(createdAt) : null;
|
|
|
+ newExif.modifyDate = null;
|
|
|
+ newExif.latitude = null;
|
|
|
+ newExif.longitude = null;
|
|
|
+ newExif.city = null;
|
|
|
+ newExif.state = null;
|
|
|
+ newExif.country = null;
|
|
|
+ newExif.fps = 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 = parseFloat(match[0]);
|
|
|
+ newExif.longitude = parseFloat(match[1]);
|
|
|
}
|
|
|
+ } 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 = parseFloat(match[1]);
|
|
|
+ newExif.longitude = parseFloat(match[2]);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- const videoTags = data.format.tags;
|
|
|
- if (videoTags) {
|
|
|
- if (videoTags['com.apple.quicktime.creationdate']) {
|
|
|
- createdAt = String(videoTags['com.apple.quicktime.creationdate']);
|
|
|
- } else if (videoTags['creation_time']) {
|
|
|
- createdAt = String(videoTags['creation_time']);
|
|
|
+ // Reverse GeoCoding
|
|
|
+ if (this.geocodingClient && newExif.longitude && newExif.latitude) {
|
|
|
+ const geoCodeInfo: MapiResponse = await this.geocodingClient
|
|
|
+ .reverseGeocode({
|
|
|
+ query: [newExif.longitude, newExif.latitude],
|
|
|
+ types: ['country', 'region', 'place'],
|
|
|
+ })
|
|
|
+ .send();
|
|
|
+
|
|
|
+ const res: [] = geoCodeInfo.body['features'];
|
|
|
+
|
|
|
+ let city = '';
|
|
|
+ let state = '';
|
|
|
+ let country = '';
|
|
|
+
|
|
|
+ if (res.filter((geoInfo) => geoInfo['place_type'][0] == 'place')[0]) {
|
|
|
+ city = res.filter((geoInfo) => geoInfo['place_type'][0] == 'place')[0]['text'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.filter((geoInfo) => geoInfo['place_type'][0] == 'region')[0]) {
|
|
|
+ state = res.filter((geoInfo) => geoInfo['place_type'][0] == 'region')[0]['text'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.filter((geoInfo) => geoInfo['place_type'][0] == 'country')[0]) {
|
|
|
+ country = res.filter((geoInfo) => geoInfo['place_type'][0] == 'country')[0]['text'];
|
|
|
+ }
|
|
|
+
|
|
|
+ newExif.city = city || null;
|
|
|
+ newExif.state = state || null;
|
|
|
+ newExif.country = country || null;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 {
|
|
|
- createdAt = asset.createdAt;
|
|
|
+ newExif.orientation = null;
|
|
|
}
|
|
|
- } else {
|
|
|
- createdAt = asset.createdAt;
|
|
|
- }
|
|
|
|
|
|
- await this.assetRepository.update({ id: asset.id }, { duration: durationString, createdAt: createdAt });
|
|
|
+ if (stream.r_frame_rate) {
|
|
|
+ let fpsParts = stream.r_frame_rate.split('/');
|
|
|
+
|
|
|
+ if (fpsParts.length === 2) {
|
|
|
+ newExif.fps = Math.round(parseInt(fpsParts[0]) / parseInt(fpsParts[1]));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- });
|
|
|
+
|
|
|
+ await this.exifRepository.save(newExif);
|
|
|
+ await this.assetRepository.update({ id: asset.id }, { duration: durationString, createdAt: createdAt });
|
|
|
+ } catch (err) {
|
|
|
+ // do nothing
|
|
|
+ console.log('Error in video metadata extraction', err);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private extractDuration(duration: number) {
|
|
@@ -202,8 +321,6 @@ export class MetadataExtractionProcessor {
|
|
|
const minutes = Math.floor((videoDurationInSecond - hours * 3600) / 60);
|
|
|
const seconds = videoDurationInSecond - hours * 3600 - minutes * 60;
|
|
|
|
|
|
- return `${hours}:${minutes < 10 ? '0' + minutes.toString() : minutes}:${
|
|
|
- seconds < 10 ? '0' + seconds.toString() : seconds
|
|
|
- }.000000`;
|
|
|
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.000000`;
|
|
|
}
|
|
|
}
|