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