github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/attachments/preview_darwin.go (about) 1 //go:build darwin 2 // +build darwin 3 4 package attachments 5 6 /* 7 #cgo CFLAGS: -x objective-c -fobjc-arc 8 #cgo LDFLAGS: -framework AVFoundation -framework CoreFoundation -framework ImageIO -framework CoreMedia -framework Foundation -framework CoreGraphics -lobjc 9 10 #include <TargetConditionals.h> 11 #include <AVFoundation/AVFoundation.h> 12 #include <CoreFoundation/CoreFoundation.h> 13 #include <Foundation/Foundation.h> 14 #include <ImageIO/ImageIO.h> 15 #if TARGET_OS_IPHONE 16 #include <MobileCoreServices/MobileCoreServices.h> 17 #endif 18 19 NSData* imageData = NULL; 20 int duration = 0; 21 22 void MakeVideoThumbnail(const char* inFilename) { 23 NSString* filename = [NSString stringWithUTF8String:inFilename]; 24 NSURL *videoURL = [NSURL fileURLWithPath:filename]; 25 26 AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil]; 27 AVAssetImageGenerator *generateImg = [[AVAssetImageGenerator alloc] initWithAsset:asset]; 28 [generateImg setAppliesPreferredTrackTransform:YES]; 29 NSError *error = NULL; 30 CMTime time = CMTimeMake(1, 1); 31 CGImageRef image = [generateImg copyCGImageAtTime:time actualTime:NULL error:&error]; 32 duration = CMTimeGetSeconds([asset duration]); 33 34 CFMutableDataRef mutableData = CFDataCreateMutable(NULL, 0); 35 CGImageDestinationRef idst = CGImageDestinationCreateWithData( 36 mutableData, kUTTypeJPEG, 1, NULL 37 ); 38 NSInteger exif = 1; 39 CGFloat compressionQuality = 0.70; 40 NSDictionary *props = [ 41 [NSDictionary alloc] 42 initWithObjectsAndKeys:[NSNumber numberWithFloat:compressionQuality], 43 kCGImageDestinationLossyCompressionQuality, 44 [NSNumber numberWithInteger:exif], 45 kCGImagePropertyOrientation, nil 46 ]; 47 CGImageDestinationAddImage(idst, image, (CFDictionaryRef)props); 48 CGImageDestinationFinalize(idst); 49 imageData = [NSData dataWithData:(__bridge_transfer NSData *)mutableData]; 50 CFRelease(idst); 51 CGImageRelease(image); 52 } 53 54 const void* ImageData() { 55 return [imageData bytes]; 56 } 57 58 int ImageLength() { 59 return [imageData length]; 60 } 61 62 int VideoDuration() { 63 return duration; 64 } 65 */ 66 import "C" 67 import ( 68 "bytes" 69 "errors" 70 "io" 71 "unsafe" 72 73 "github.com/keybase/client/go/chat/types" 74 "github.com/keybase/client/go/chat/utils" 75 "golang.org/x/net/context" 76 ) 77 78 func previewVideo(ctx context.Context, log utils.DebugLabeler, src io.Reader, 79 basename string, nvh types.NativeVideoHelper) (res *PreviewRes, err error) { 80 defer log.Trace(ctx, &err, "previewVideo")() 81 cbasename := C.CString(basename) 82 defer C.free(unsafe.Pointer(cbasename)) 83 C.MakeVideoThumbnail(cbasename) 84 duration := int(C.VideoDuration()) 85 if duration < 1 { 86 // clamp to 1 so we know it is a video, but also not to compute a duration for it 87 duration = 1 88 } else { 89 duration *= 1000 90 } 91 log.Debug(ctx, "previewVideo: length: %d duration: %ds", C.ImageLength(), duration) 92 if C.ImageLength() == 0 { 93 return res, errors.New("no data returned from native") 94 } 95 localDat := make([]byte, C.ImageLength()) 96 copy(localDat, (*[1 << 30]byte)(C.ImageData())[0:C.ImageLength()]) 97 imagePreview, err := previewImage(ctx, log, bytes.NewReader(localDat), basename, "image/jpeg") 98 if err != nil { 99 return res, err 100 } 101 return &PreviewRes{ 102 Source: imagePreview.Source, 103 ContentType: "image/jpeg", 104 BaseWidth: imagePreview.BaseWidth, 105 BaseHeight: imagePreview.BaseHeight, 106 BaseDurationMs: duration, 107 PreviewHeight: imagePreview.PreviewHeight, 108 PreviewWidth: imagePreview.PreviewWidth, 109 }, nil 110 } 111 112 func LinkNoop() {}