@ -13,6 +13,7 @@ enum SignalAttachmentError: Error {
case invalidData
case couldNotParseImage
case couldNotConvertToJpeg
case couldNotConvertToMpeg4
case invalidFileFormat
}
@ -49,6 +50,8 @@ extension SignalAttachmentError: LocalizedError {
return NSLocalizedString ( " ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG " , comment : " Attachment error message for image attachments which could not be converted to JPEG " )
case . invalidFileFormat :
return NSLocalizedString ( " ATTACHMENT_ERROR_INVALID_FILE_FORMAT " , comment : " Attachment error message for attachments with an invalid file format " )
case . couldNotConvertToMpeg4 :
return NSLocalizedString ( " ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4 " , comment : " Attachment error message for video attachments which could not be converted to MP4 " )
}
}
}
@ -353,6 +356,10 @@ public class SignalAttachment: NSObject {
return MIMETypeUtil . supportedImageUTITypes ( ) . union ( animatedImageUTISet )
}
private class var outputVideoUTISet : Set < String > {
return Set ( [ kUTTypeMPEG4 as String ] )
}
// R e t u r n s t h e s e t o f U T I s t h a t c o r r e s p o n d t o v a l i d a n i m a t e d i m a g e f o r m a t s
// f o r S i g n a l a t t a c h m e n t s .
private class var animatedImageUTISet : Set < String > {
@ -767,10 +774,91 @@ public class SignalAttachment: NSObject {
// N O T E : T h e a t t a c h m e n t r e t u r n e d b y t h i s m e t h o d m a y n o t b e v a l i d .
// C h e c k t h e a t t a c h m e n t ' s e r r o r p r o p e r t y .
private class func videoAttachment ( dataSource : DataSource ? , dataUTI : String ) -> SignalAttachment {
return newAttachment ( dataSource : dataSource ,
dataUTI : dataUTI ,
validUTISet : videoUTISet ,
maxFileSize : kMaxFileSizeVideo )
guard let dataSource = dataSource else {
let dataSource = DataSourceValue . emptyDataSource ( )
let attachment = SignalAttachment ( dataSource : dataSource , dataUTI : dataUTI )
attachment . error = . missingData
return attachment
}
if isInputVideoValidOutputVideo ( dataSource : dataSource , dataUTI : dataUTI ) {
return newAttachment ( dataSource : dataSource ,
dataUTI : dataUTI ,
validUTISet : videoUTISet ,
maxFileSize : kMaxFileSizeVideo )
} else {
// c o n v e r t t o m p 4
return compressVideoAsMp4 ( dataSource : dataSource , dataUTI : dataUTI )
}
}
class var videoTempPath : URL {
let videoDir = URL ( fileURLWithPath : NSTemporaryDirectory ( ) ) . appendingPathComponent ( " video " )
OWSFileSystem . ensureDirectoryExists ( videoDir . path )
return videoDir
}
class func compressVideoAsMp4 ( dataSource : DataSource , dataUTI : String ) -> SignalAttachment {
Logger . debug ( " \( self . TAG ) in \( #function ) " )
guard let url = dataSource . dataUrl ( ) else {
let attachment = SignalAttachment ( dataSource : DataSourceValue . emptyDataSource ( ) , dataUTI : dataUTI )
attachment . error = . missingData
return attachment
}
let asset = AVAsset ( url : url )
guard let exportSession = AVAssetExportSession ( asset : asset , presetName : AVAssetExportPresetMediumQuality ) else {
let attachment = SignalAttachment ( dataSource : DataSourceValue . emptyDataSource ( ) , dataUTI : dataUTI )
attachment . error = . couldNotConvertToMpeg4
return attachment
}
exportSession . shouldOptimizeForNetworkUse = true
exportSession . outputFileType = AVFileTypeMPEG4
let exportURL = videoTempPath . appendingPathComponent ( UUID ( ) . uuidString ) . appendingPathExtension ( " mp4 " )
exportSession . outputURL = exportURL
Logger . debug ( " \( self . TAG ) starting video export " )
let semaphore = DispatchSemaphore ( value : 0 )
exportSession . exportAsynchronously {
Logger . debug ( " \( self . TAG ) Completed video export " )
semaphore . signal ( )
}
// F I X M E m a k e t h e A P I a s y n c , r e t u r n p r o g r e s s .
Logger . debug ( " \( self . TAG ) Waiting for video export " )
semaphore . wait ( )
Logger . debug ( " \( self . TAG ) Done waiting for video export " )
let baseFilename = dataSource . sourceFilename
let mp4Filename = baseFilename ? . filenameWithoutExtension . appendingFileExtension ( " mp4 " )
guard let dataSource = DataSourcePath . dataSource ( with : exportURL ) else {
owsFail ( " Failed to build data source for exported video URL " )
let attachment = SignalAttachment ( dataSource : DataSourceValue . emptyDataSource ( ) , dataUTI : dataUTI )
attachment . error = . couldNotConvertToMpeg4
return attachment
}
dataSource . sourceFilename = mp4Filename
return SignalAttachment ( dataSource : dataSource , dataUTI : kUTTypeMPEG4 as String )
}
class func isInputVideoValidOutputVideo ( dataSource : DataSource ? , dataUTI : String ) -> Bool {
guard let dataSource = dataSource else {
return false
}
guard SignalAttachment . outputVideoUTISet . contains ( dataUTI ) else {
return false
}
if dataSource . dataLength ( ) <= kMaxFileSizeVideo {
return true
}
return false
}
// MARK: A u d i o A t t a c h m e n t s