1
0
Fork 0
mirror of https://github.com/mautrix/discord.git synced 2026-07-07 06:43:18 -04:00
mautrix-discord/pkg/msgconv/voicemsg.go

124 lines
3 KiB
Go
Raw Permalink Normal View History

// mautrix-discord - A Matrix-Discord puppeting bridge.
// Copyright (C) 2026 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package msgconv
import (
"strings"
"maunium.net/go/mautrix/event"
)
type discordVoiceMetadata struct {
ContentType string
DurationSeconds float64
Waveform []byte
}
// NOTE these waveform calculations are pure conjecture; i.e. they aren't
// modeled after what first-party clients actually do
func waveformBuckets(durationMs int) int {
targetLength := (durationMs + 99) / 100 // like math.Ceil(durationMs / 100)
targetLength = max(min(targetLength, 256), 1) // clamp to [1,256]
return targetLength
}
func downsampleWaveform(waveform []int, buckets int) []int {
if len(waveform) <= buckets {
return waveform
}
samples := make([]int, 0, buckets)
for i := range buckets {
start := i * len(waveform) / buckets
end := (i + 1) * len(waveform) / buckets
if end <= start {
end = start + 1
}
maxVal := waveform[start]
for _, sample := range waveform[start+1 : end] {
if sample > maxVal {
maxVal = sample
}
}
samples = append(samples, maxVal)
}
return samples
}
func matrixWaveformToDiscord(samples []int, durationMs int) []byte {
if len(samples) == 0 {
return nil
}
samples = downsampleWaveform(samples, waveformBuckets(durationMs))
maxVal := 0
for _, sample := range samples {
if sample > maxVal {
maxVal = sample
}
}
clampedSamples := make([]byte, len(samples))
for i, sample := range samples {
if maxVal > 256 {
sample /= 4
}
if sample < 0 {
sample = 0
}
if sample > 255 {
sample = 255
}
clampedSamples[i] = byte(sample)
}
return clampedSamples
}
func getDiscordVoiceMetadata(content *event.MessageEventContent) *discordVoiceMetadata {
if content.MSC3245Voice == nil || content.MSC1767Audio == nil {
return nil
}
mimeType := strings.TrimSpace(content.Info.MimeType)
if !strings.HasPrefix(strings.ToLower(mimeType), "audio/") {
return nil
}
durationMs := content.Info.Duration
if durationMs == 0 {
durationMs = content.MSC1767Audio.Duration
}
if durationMs <= 0 {
return nil
}
waveform := matrixWaveformToDiscord(content.MSC1767Audio.Waveform, durationMs)
if len(waveform) == 0 {
return nil
}
return &discordVoiceMetadata{
ContentType: mimeType,
DurationSeconds: float64(durationMs) / 1000,
Waveform: waveform,
}
}