mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 10:27:08 +02:00
The FLAC and mp3 repairs had grown into two copies of the same wrapper: the same lazy peek on the first Read, the same MultiReader hand-off, the same tolerance of a short read, and the same comment twice. They now share one headerPatcher, with a prefix function per container. Transcode no longer selects the patcher by opts.Format. That string is the target format of a transcoding profile, which users can name anything, so a row called 'mp3 320' running the default mp3 command got no repair at all, while a row whose command emits FLAC got one only because the FLAC arm was the default. The shared peek dispatches on the magic bytes instead, which both patchers already had to check, and leaves anything it does not recognize untouched. Also drops b2i in favour of gg.If, and lowers the ID3 bound to 64KB: ffmpeg cannot write an attached picture to a non-seekable output, so the tag ffmpeg pipes out stays small.
61 lines
1.6 KiB
Go
61 lines
1.6 KiB
Go
package ffmpeg
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// patchPipedHeader repairs the header ffmpeg cannot finish when its output is a pipe,
|
|
// since it only learns the sample or frame count once it can no longer rewind.
|
|
func patchPipedHeader(r io.ReadCloser, duration float32) io.ReadCloser {
|
|
if duration <= 0 {
|
|
return r
|
|
}
|
|
return &headerPatcher{ReadCloser: r, duration: duration}
|
|
}
|
|
|
|
type headerPatcher struct {
|
|
io.ReadCloser
|
|
duration float32
|
|
// Peeking here rather than in the constructor keeps Transcode from blocking
|
|
// until ffmpeg has emitted its first bytes.
|
|
stream io.Reader
|
|
}
|
|
|
|
func (h *headerPatcher) Read(p []byte) (int, error) {
|
|
if h.stream == nil {
|
|
prefix, err := h.peek()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
h.stream = io.MultiReader(bytes.NewReader(prefix), h.ReadCloser)
|
|
}
|
|
return h.stream.Read(p)
|
|
}
|
|
|
|
// peek dispatches on the magic bytes rather than on the requested format, which is a
|
|
// free-text label on the transcoding profile and need not match what ffmpeg emits.
|
|
func (h *headerPatcher) peek() ([]byte, error) {
|
|
buf, err := h.fill(nil, 4)
|
|
if err != nil || len(buf) < 4 {
|
|
return buf, err
|
|
}
|
|
if string(buf) == "fLaC" {
|
|
return h.flacPrefix(buf)
|
|
}
|
|
return h.mp3Prefix(buf)
|
|
}
|
|
|
|
// fill grows buf to n bytes, stopping short when the stream ends first.
|
|
func (h *headerPatcher) fill(buf []byte, n int) ([]byte, error) {
|
|
if len(buf) >= n {
|
|
return buf, nil
|
|
}
|
|
more := make([]byte, n-len(buf))
|
|
read, err := io.ReadFull(h.ReadCloser, more)
|
|
if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
|
|
return buf, err
|
|
}
|
|
return append(buf, more[:read]...), nil
|
|
}
|