1package attachments
2
3import (
4	"bytes"
5	"image"
6	"image/color"
7	"image/png"
8	"math"
9
10	"github.com/keybase/client/go/protocol/chat1"
11	"golang.org/x/net/context"
12)
13
14type audioVisualizer struct {
15	amps        []float64
16	bkgColor    color.Color
17	strokeColor color.Color
18	strokeWidth int
19	strokeGap   int
20	height      int
21	minAmp      float64
22}
23
24func newAudioVisualizer(amps []float64) *audioVisualizer {
25	return &audioVisualizer{
26		amps:        amps,
27		bkgColor:    color.White,
28		strokeColor: color.Black,
29		strokeWidth: 1,
30		strokeGap:   1,
31		height:      64,
32		minAmp:      -80,
33	}
34}
35
36func (a *audioVisualizer) stroke(offset, height int, img *image.NRGBA) {
37	for i := offset; i < offset+a.strokeWidth; i++ {
38		for j := 0; j < height; j++ {
39			img.Set(i, a.height-j, a.strokeColor)
40		}
41	}
42}
43
44func (a *audioVisualizer) getHeight(amp float64) int {
45	prop := math.Min(1.0, math.Max(1.0-amp/a.minAmp, 0.1))
46	return int(float64(a.height) * prop)
47}
48
49func (a *audioVisualizer) visualize() ([]byte, int) {
50	numStrokes := len(a.amps)
51	width := numStrokes * (a.strokeWidth + a.strokeGap)
52	img := image.NewNRGBA(image.Rect(0, 0, width, a.height))
53	offset := 0
54	for i := 0; i < width; i++ {
55		for j := 0; j < a.height; j++ {
56			img.Set(i, j, a.bkgColor)
57		}
58	}
59	for i := 0; i < numStrokes; i++ {
60		height := a.getHeight(a.amps[i])
61		a.stroke(offset, height, img)
62		offset += a.strokeWidth + a.strokeGap
63	}
64	var buf bytes.Buffer
65	_ = png.Encode(&buf, img)
66	return buf.Bytes(), width
67}
68
69func (s *Sender) MakeAudioPreview(ctx context.Context, amps []float64, duration int) (res chat1.MakePreviewRes, err error) {
70	defer s.Trace(ctx, &err, "MakeAudioPreview")()
71	v := newAudioVisualizer(amps)
72	previewDat, previewWidth := v.visualize()
73	res.MimeType = "video/mp4"
74	res.PreviewMimeType = new(string)
75	*res.PreviewMimeType = "image/png"
76	location := chat1.NewPreviewLocationWithBytes(previewDat)
77	res.Location = &location
78	baseMd := chat1.NewAssetMetadataWithVideo(chat1.AssetMetadataVideo{
79		Width:      previewWidth,
80		Height:     v.height,
81		DurationMs: duration,
82		IsAudio:    true,
83	})
84	res.BaseMetadata = &baseMd
85	previewMd := chat1.NewAssetMetadataWithImage(chat1.AssetMetadataImage{
86		Width:     previewWidth,
87		Height:    v.height,
88		AudioAmps: amps,
89	})
90	res.Metadata = &previewMd
91	return res, nil
92}
93