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