github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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 -framework AppKit -framework UniformTypeIdentifiers -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  #include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
    16  #if TARGET_OS_IPHONE
    17  #include <MobileCoreServices/MobileCoreServices.h>
    18  #include <UIKit/UIKit.h>
    19  #else
    20  #include <AppKit/AppKit.h>
    21  #endif
    22  
    23  NSData* imageData = NULL;
    24  int duration = 0;
    25  
    26  void MakeVideoThumbnail(const char* inFilename) {
    27  	NSString* filename = [NSString stringWithUTF8String:inFilename];
    28  	NSURL *videoURL = [NSURL fileURLWithPath:filename];
    29  
    30  	AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
    31  	AVAssetImageGenerator *generateImg = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    32  	[generateImg setAppliesPreferredTrackTransform:YES];
    33  	NSError *error = NULL;
    34  	CMTime time = CMTimeMake(1, 1);
    35  	CGImageRef image = [generateImg copyCGImageAtTime:time actualTime:NULL error:&error];
    36  	duration = CMTimeGetSeconds([asset duration]);
    37  
    38  	CFMutableDataRef mutableData = CFDataCreateMutable(NULL, 0);
    39  	CGImageDestinationRef idst = CGImageDestinationCreateWithData(
    40  		mutableData, (CFStringRef)UTTypeJPEG.identifier, 1, NULL
    41  	);
    42  	NSInteger exif             =    1;
    43  	CGFloat compressionQuality = 0.70;
    44  	NSDictionary *props = [
    45  		[NSDictionary alloc]
    46  		initWithObjectsAndKeys:[NSNumber numberWithFloat:compressionQuality],
    47  		kCGImageDestinationLossyCompressionQuality,
    48  		[NSNumber numberWithInteger:exif],
    49  		kCGImagePropertyOrientation, nil
    50  	];
    51  	CGImageDestinationAddImage(idst, image, (CFDictionaryRef)props);
    52  	CGImageDestinationFinalize(idst);
    53  	imageData = [NSData dataWithData:(__bridge_transfer NSData *)mutableData];
    54  	CFRelease(idst);
    55  	CGImageRelease(image);
    56  }
    57  
    58  const void* ImageData() {
    59  	return [imageData bytes];
    60  }
    61  
    62  int ImageLength() {
    63  	return [imageData length];
    64  }
    65  
    66  int VideoDuration() {
    67  	return duration;
    68  }
    69  
    70  #if TARGET_OS_IPHONE
    71  int HEICToJPEG(const char* inFilename) {
    72  	NSString* filename = [NSString stringWithUTF8String:inFilename];
    73  	UIImage* heicImage = [UIImage imageWithContentsOfFile:filename];
    74  	if (heicImage) {
    75  		imageData = UIImageJPEGRepresentation(heicImage, 1.0);
    76  		return 0;
    77  	}
    78  	return 1;
    79  }
    80  #else
    81  int HEICToJPEG(const char* inFilename) {
    82  	NSString* filename = [NSString stringWithUTF8String:inFilename];
    83  	NSImage *heicImage = [[NSImage alloc] initWithContentsOfFile:filename];
    84      if (heicImage) {
    85          NSArray<NSImageRep *> *imageReps = [heicImage representations];
    86          NSImageRep *imageRep = [imageReps firstObject];
    87          if (imageRep) {
    88              NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)imageRep;
    89              if (bitmapRep) {
    90                  imageData = [bitmapRep representationUsingType:NSBitmapImageFileTypeJPEG properties:@{}];
    91  				return 0;
    92              }
    93          }
    94      }
    95  	return 1;
    96  }
    97  #endif
    98  */
    99  import "C"
   100  import (
   101  	"bytes"
   102  	"errors"
   103  	"io"
   104  	"unsafe"
   105  
   106  	"github.com/keybase/client/go/chat/types"
   107  	"github.com/keybase/client/go/chat/utils"
   108  	"golang.org/x/net/context"
   109  )
   110  
   111  func previewVideo(ctx context.Context, log utils.DebugLabeler, src io.Reader,
   112  	basename string, nvh types.NativeVideoHelper) (res *PreviewRes, err error) {
   113  	defer log.Trace(ctx, &err, "previewVideo")()
   114  	cbasename := C.CString(basename)
   115  	defer C.free(unsafe.Pointer(cbasename))
   116  	C.MakeVideoThumbnail(cbasename)
   117  	duration := int(C.VideoDuration())
   118  	if duration < 1 {
   119  		// clamp to 1 so we know it is a video, but also not to compute a duration for it
   120  		duration = 1
   121  	} else {
   122  		duration *= 1000
   123  	}
   124  	log.Debug(ctx, "previewVideo: length: %d duration: %ds", C.ImageLength(), duration)
   125  	if C.ImageLength() == 0 {
   126  		return res, errors.New("no data returned from native")
   127  	}
   128  	localDat := make([]byte, C.ImageLength())
   129  	copy(localDat, (*[1 << 30]byte)(C.ImageData())[0:C.ImageLength()])
   130  	imagePreview, err := previewImage(ctx, log, bytes.NewReader(localDat), basename, "image/jpeg")
   131  	if err != nil {
   132  		return res, err
   133  	}
   134  	return &PreviewRes{
   135  		Source:         imagePreview.Source,
   136  		ContentType:    "image/jpeg",
   137  		BaseWidth:      imagePreview.BaseWidth,
   138  		BaseHeight:     imagePreview.BaseHeight,
   139  		BaseDurationMs: duration,
   140  		PreviewHeight:  imagePreview.PreviewHeight,
   141  		PreviewWidth:   imagePreview.PreviewWidth,
   142  	}, nil
   143  }
   144  
   145  func HEICToJPEG(ctx context.Context, log utils.DebugLabeler, basename string) (dat []byte, err error) {
   146  	defer log.Trace(ctx, &err, "HEICToJPEG")()
   147  	cbasename := C.CString(basename)
   148  	defer C.free(unsafe.Pointer(cbasename))
   149  	ret := C.HEICToJPEG(cbasename)
   150  	log.Debug(ctx, "HEICToJPEG: length: %d", C.ImageLength())
   151  	if ret != 0 || C.ImageLength() == 0 {
   152  		log.Debug(ctx, "unable to convert heic to jpeg")
   153  		return nil, nil
   154  	}
   155  	dat = make([]byte, C.ImageLength())
   156  	copy(dat, (*[1 << 30]byte)(C.ImageData())[0:C.ImageLength()])
   157  	return dat, nil
   158  }
   159  
   160  func LinkNoop() {}