mirror of
https://github.com/mautrix/discord.git
synced 2026-07-06 22:33:18 -04:00
124 lines
3 KiB
Go
124 lines
3 KiB
Go
// 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,
|
|
}
|
|
}
|