github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/attachments/audio.go (about)

     1  package attachments
     2  
     3  import (
     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  
    14  type 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  
    24  func 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  
    36  func (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  
    44  func (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  
    49  func (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  
    69  func (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  }