@ -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 ` ;
}
}