Du betrachtest gerade Matrix Server LINE Patch für die LINE Bridge

Matrix Server LINE Patch für die LINE Bridge

Der Matrix Server LINE Patch für die LINE Bridge behebt das Problem mit den Audio.- und Video Nachrichten die nicht verarbeitet werden.

Kurz erklärt

LINE und Element verstehen Sprachnachrichten jeweils nur in „ihrem“ Format – wie zwei Personen, die unterschiedliche Sprachen sprechen. Die LINE Bridge hat die Nachrichten bisher nur weitergereicht, ohne zu übersetzen. Der Patch baut den fehlenden Übersetzer ein. Danach funktionieren Sprachnachrichten in beide Richtungen, und laut Praxistest auch Videonachrichten.

Dieser Beitrag zeigt dir Schritt für Schritt, was du tun musst und in welchem Ordner.

1. Was war das Problem?

  • Von Element zu LINE: Die Sprachnachricht ist in Element in Ordnung. In der LINE-App erscheint aber nur ein Platzhalter, abspielen geht nicht.
  • Von LINE zu Element: Die Nachricht kommt an und lässt sich abspielen, wird aber wie eine normale Musikdatei („MP3“) angezeigt. Der gewohnte Sprachnachrichten-Player fehlt.

Warum? Es liegt weder an deiner Einrichtung noch an Matrix oder Synapse. Die Ursache steckt in der Bridge selbst:

  • Element nimmt Sprachnachrichten in einem Format auf, das LINE nicht abspielen kann. LINE verwendet ein anderes Format. Die Bridge hat die Datei einfach unverändert weitergegeben.
  • In der Gegenrichtung fehlte der Nachricht ein Kennzeichen, das Element sagt: „Das ist eine Sprachnachricht“. Ohne dieses Kennzeichen zeigt Element nur den normalen Datei-Player.

Im Log tauchen manchmal Zeilen mit op_type 43 auf. Sie sind harmlos und nur das Echo der eigenen Nachricht.

Video: Matrix Server LINE Patch für die LINE Bridge

Sprache: 🇩🇪|🇬🇧
☝️ Benutze YouTube Untertitel für alle Sprachen.

2. Was der Patch ändert

Ein „Patch“ ist eine Art Korrekturliste: Eine Textdatei beschreibt genau, welche Zeilen in welchen Dateien geändert werden. Du musst nichts von Hand programmieren. Dieser Patch betrifft drei Dateien:

  1. audioconv.go (neu): Der Übersetzer. Er nutzt das Programm ffmpeg, das in der Bridge bereits eingebaut ist, und wandelt Audio in das jeweils passende Format um.
  2. audio.go (geändert): Gilt für Nachrichten von LINE zu Element. Die Sprachnachricht wird übersetzt und mit dem Kennzeichen „Sprachnachricht“, der Dauer und der kleinen Wellenform versehen. Klappt die Übersetzung nicht, kommt wie bisher die Originaldatei an, es geht also nichts verloren.
  3. send_message.go (geändert): Gilt für Nachrichten von Element zu LINE. Die Sprachnachricht wird vor dem Senden in das LINE-Format übersetzt.

Matrix, Synapse, Element und deine Einstellungen (config.yaml) bleiben unberührt.

3. Bevor du startest

💡 Datensicherung wird empfohlen: Eine kurze Sicherung deines Setups vorab sorgt dafür, dass du dich entspannt zurücklehnen kannst.

Du brauchst:

  • Zugriff auf den Ordner matrix auf deinem Server (dort liegt die Datei docker-compose.yml)
  • ein Terminal (Konsole) auf dem Server, auf dem die Docker-Container laufen

4. Wo liegt was?

Ausgangspunkt ist der Ordner matrix mit der Datei docker-compose.yml.

  • Neue Datei, die du selbst anlegst: matrix/line-bridge/line-audio.patch. Den Inhalt findest du im Anhang am Ende dieses Beitrags.
  • Dateien, die der Patch automatisch ändert (du musst sie nicht öffnen), alle im Ordner matrix/line-bridge/matrix-line-messenger/pkg/connector/:
    • handlers/audioconv.go (wird neu angelegt)
    • handlers/audio.go (wird geändert)
    • send_message.go (wird geändert)

5. Schritt für Schritt

Schritt 1: Patch-Datei anlegen. Erstelle im Ordner matrix/line-bridge/ eine neue Textdatei mit dem Namen line-audio.patch. Kopiere den kompletten Inhalt aus dem Anhang hinein, ohne etwas zu verändern (auch Leerzeichen und Einrückungen müssen so bleiben). Speichere sie als reinen Text, ohne zusätzliche Endung wie .txt.

Schritt 2: In den Quellcode-Ordner wechseln. Öffne das Terminal auf dem Server und gehe in den Ordner matrix. Von dort:

cd line-bridge/matrix-line-messenger

Schritt 3: Trockenlauf. Das ist nur ein Probelauf, es wird noch nichts verändert:

patch -p1 --dry-run < ../line-audio.patch

Es sollten nur Zeilen mit „checking file …“ erscheinen. Steht dort „FAILED“, bitte nicht weitermachen, sondern prüfen, ob der Inhalt der Patch-Datei vollständig und unverändert kopiert wurde.

Schritt 4: Patch einspielen.

patch -p1 < ../line-audio.patch

Schritt 5 (empfohlen): Prüfen, ob der Übersetzer beide „Sprachen“ kann.

docker exec matrix-line-bridge ffmpeg -hide_banner -encoders | grep -E "libopus| aac "

Es müssen zwei Zeilen erscheinen, eine mit libopus und eine mit aac.

Schritt 6: Bridge neu bauen und starten. Zurück in den Ordner matrix und dann:

cd ../..
docker compose build line-bridge
docker compose up -d line-bridge

Nur die Bridge wird neu gestartet. Synapse und Element laufen unverändert weiter. Bricht der Bau mit einer Fehlermeldung ab, läuft die Bridge in der alten Version weiter.

6. Testen

  1. Nimm in Element eine Sprachnachricht in einem LINE-Chat auf und sende sie. In der LINE-App muss sie sich abspielen lassen.
  2. Sende aus LINE eine Sprachnachricht. In Element muss sie im gewohnten Sprachnachrichten-Player mit Wellenform erscheinen.
  3. Sende je ein Video in beide Richtungen und spiele es ab.

Alte Nachrichten ändern sich nicht. Nur neue Nachrichten werden übersetzt.

7. Zurück zum alten Stand

Falls du den Patch wieder entfernen möchtest, im Ordner matrix/line-bridge/matrix-line-messenger:

patch -R -p1 < ../line-audio.patch
cd ../..
docker compose build line-bridge && docker compose up -d line-bridge

8. Gut zu wissen

  • Der Übersetzer wurde mit echten Sprachnachrichten getestet. Auf dem Server funktionieren Sprachnachrichten und, laut Praxistest, auch Videonachrichten. Der Langzeittest läuft noch.
  • Telefon- und Videoanrufe werden von der Bridge nur als Hinweis-Nachricht angezeigt. Daran ändert der Patch nichts.
  • Wird die Bridge später auf eine neuere Version aktualisiert, muss der Patch erneut eingespielt werden. Eventuell sind kleine Anpassungen nötig.
  • Der Patch bezieht sich auf den Stand c40908e (22.09.2026) der Bridge.

Anhang: Inhalt der Datei line-audio.patch

Den folgenden Text komplett kopieren und in matrix/line-bridge/line-audio.patch speichern:

--- a/pkg/connector/handlers/audio.go
+++ b/pkg/connector/handlers/audio.go
@@ -99,14 +99,43 @@
  }
  }
 
- mxc, file, err := intent.UploadMedia(ctx, portal.MXID, audioData, "audio.m4a", "audio/mp4")
+ // LINE voice messages are AAC/M4A. Matrix clients (Element Web/X) only show the
+ // voice message player for Ogg/Opus with the MSC3245 voice marker, so convert.
+ // If anything fails, fall back to the original M4A file (previous behaviour).
+ uploadData, uploadName, uploadMime := audioData, "audio.m4a", "audio/mp4"
+ var extra map[string]any
+ if oggData, convErr := ConvertAudioToOggOpus(ctx, audioData); convErr != nil {
+ h.Log.Warn().Err(convErr).Str("msg_id", data.ID).
+ Msg("Failed to convert LINE audio to Ogg/Opus, sending original M4A")
+ } else {
+ uploadData, uploadName, uploadMime = oggData, "Voice message.ogg", "audio/ogg"
+
+ waveform, analyzedMs, anaErr := AnalyzeAudio(ctx, audioData, 100)
+ if anaErr != nil {
+ h.Log.Warn().Err(anaErr).Str("msg_id", data.ID).
+ Msg("Failed to compute waveform for LINE voice message")
+ }
+ if duration <= 0 {
+ duration = analyzedMs
+ }
+ audioBlock := map[string]any{"duration": duration}
+ if len(waveform) > 0 {
+ audioBlock["waveform"] = waveform
+ }
+ extra = map[string]any{
+ "org.matrix.msc3245.voice": map[string]any{},
+ "org.matrix.msc1767.audio": audioBlock,
+ }
+ }
+
+ mxc, file, err := intent.UploadMedia(ctx, portal.MXID, uploadData, uploadName, uploadMime)
  if err != nil {
  return nil, fmt.Errorf("failed to upload audio to matrix: %w", err)
  }
 
  audioInfo := &event.FileInfo{
- MimeType: "audio/mp4",
- Size:     len(audioData),
+ MimeType: uploadMime,
+ Size:     len(uploadData),
  }
  if duration > 0 {
  audioInfo.Duration = duration
@@ -118,12 +147,13 @@
  Type: event.EventMessage,
  Content: &event.MessageEventContent{
  MsgType:   event.MsgAudio,
- Body:      "audio.m4a",
+ Body:      uploadName,
  URL:       mxc,
  File:      file,
  Info:      audioInfo,
  RelatesTo: relatesTo,
  },
+ Extra: extra,
  },
  },
  }, nil
--- a/pkg/connector/send_message.go
+++ b/pkg/connector/send_message.go
@@ -19,6 +19,7 @@
  "maunium.net/go/mautrix/bridgev2/networkid"
  "maunium.net/go/mautrix/event"
 
+ "github.com/highesttt/matrix-line-messenger/pkg/connector/handlers"
  "github.com/highesttt/matrix-line-messenger/pkg/e2ee"
  "github.com/highesttt/matrix-line-messenger/pkg/line"
  "github.com/highesttt/matrix-line-messenger/pkg/ltsm"
@@ -524,6 +525,20 @@
  return nil, fmt.Errorf("failed to download audio from matrix: %w", err)
  }
 
+ // LINE's apps play voice messages as AAC/M4A. Element sends Ogg/Opus, which LINE
+ // shows only as an unplayable placeholder, so convert anything that isn't M4A yet.
+ if !handlers.IsMP4Audio(data) {
+ m4aData, convErr := handlers.ConvertAudioToM4A(ctx, data)
+ if convErr != nil {
+ return nil, fmt.Errorf("failed to convert audio to m4a for LINE: %w", convErr)
+ }
+ lc.UserLogin.Bridge.Log.Info().
+ Int("original_size", len(data)).
+ Int("converted_size", len(m4aData)).
+ Msg("Converted Matrix audio to M4A for LINE")
+ data = m4aData
+ }
+
  contentType = int(ContentAudio)
  contentMetadata["FILE_SIZE"] = fmt.Sprintf("%d", len(data))
  contentMetadata["contentType"] = fmt.Sprintf("%d", ContentAudio)
--- a/pkg/connector/handlers/audioconv.go
+++ b/pkg/connector/handlers/audioconv.go
@@ -0,0 +1,113 @@
+package handlers
+
+import (
+ "bytes"
+ "context"
+ "encoding/binary"
+ "fmt"
+ "math"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "time"
+)
+
+const audioConvertTimeout = 90 * time.Second
+
+// ffmpegAudio writes data to a temp file, runs ffmpeg with the given output
+// arguments and returns the produced file. Temp files (instead of pipes) are
+// used on purpose: MP4/M4A needs seekable input and output.
+func ffmpegAudio(ctx context.Context, data []byte, outExt string, outArgs ...string) ([]byte, error) {
+ dir, err := os.MkdirTemp("", "line-audio-*")
+ if err != nil {
+ return nil, err
+ }
+ defer os.RemoveAll(dir)
+
+ in := filepath.Join(dir, "in.bin")
+ out := filepath.Join(dir, "out"+outExt)
+ if err = os.WriteFile(in, data, 0o600); err != nil {
+ return nil, err
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, audioConvertTimeout)
+ defer cancel()
+
+ args := []string{"-hide_banner", "-loglevel", "error", "-nostdin", "-y", "-i", in, "-vn"}
+ args = append(args, outArgs...)
+ args = append(args, out)
+
+ cmd := exec.CommandContext(ctx, "ffmpeg", args...)
+ var stderr bytes.Buffer
+ cmd.Stderr = &stderr
+ if err = cmd.Run(); err != nil {
+ return nil, fmt.Errorf("ffmpeg failed: %w: %s", err, stderr.String())
+ }
+ return os.ReadFile(out)
+}
+
+// ConvertAudioToM4A converts arbitrary audio (e.g. Ogg/Opus voice messages from
+// Matrix clients) to AAC-LC in an M4A container, mono 16 kHz. That is the same
+// format LINE's own apps use for voice messages.
+func ConvertAudioToM4A(ctx context.Context, data []byte) ([]byte, error) {
+ return ffmpegAudio(ctx, data, ".m4a",
+ "-ac", "1", "-ar", "16000", "-c:a", "aac", "-b:a", "32k",
+ "-movflags", "+faststart", "-f", "ipod")
+}
+
+// ConvertAudioToOggOpus converts audio (LINE voice messages are AAC/M4A) to
+// Ogg/Opus, the format required for Matrix voice messages (MSC3245).
+func ConvertAudioToOggOpus(ctx context.Context, data []byte) ([]byte, error) {
+ return ffmpegAudio(ctx, data, ".ogg",
+ "-ac", "1", "-ar", "48000", "-c:a", "libopus", "-b:a", "24k",
+ "-application", "voip", "-f", "ogg")
+}
+
+// AnalyzeAudio decodes the audio to 8 kHz mono PCM and returns a waveform with
+// the given number of buckets (values 0..1024, as used by MSC1767/MSC3246) and
+// the duration in milliseconds.
+func AnalyzeAudio(ctx context.Context, data []byte, buckets int) (waveform []int, durationMs int, err error) {
+ const rate = 8000
+ pcm, err := ffmpegAudio(ctx, data, ".pcm", "-ac", "1", "-ar", fmt.Sprint(rate), "-f", "s16le")
+ if err != nil {
+ return nil, 0, err
+ }
+ n := len(pcm) / 2
+ if n == 0 {
+ return nil, 0, fmt.Errorf("decoded audio is empty")
+ }
+ durationMs = n * 1000 / rate
+ if buckets <= 0 || buckets > n {
+ buckets = min(max(buckets, 1), n)
+ }
+
+ rms := make([]float64, buckets)
+ peak := 0.0
+ for b := 0; b < buckets; b++ {
+ from, to := b*n/buckets, (b+1)*n/buckets
+ if to <= from {
+ to = from + 1
+ }
+ var sum float64
+ for i := from; i < to; i++ {
+ s := float64(int16(binary.LittleEndian.Uint16(pcm[i*2:])))
+ sum += s * s
+ }
+ rms[b] = math.Sqrt(sum / float64(to-from))
+ peak = math.Max(peak, rms[b])
+ }
+
+ waveform = make([]int, buckets)
+ if peak > 0 {
+ for i, v := range rms {
+ waveform[i] = int(math.Round(v / peak * 1024))
+ }
+ }
+ return waveform, durationMs, nil
+}
+
+// IsMP4Audio reports whether data already looks like an MP4/M4A container
+// ("ftyp" box right at the start).
+func IsMP4Audio(data []byte) bool {
+ return len(data) >= 12 && bytes.Equal(data[4:8], []byte("ftyp"))
+}

Quellen


Spenden Bild

Link zur Unterstützung / Spende für den Kanal
Wenn meine Beiträge hilfreich sind oder dir geholfen haben, würde ich mich über eine Unterstützung sehr freuen 🙏

PayPal Link
Überweisung, Bitcoin und Lightning


#Matrix #LINE #Bridge #SelfHosting #Synapse

Schreibe einen Kommentar