navidrome/core/transcoder/transcoder.go
Deluan Quintão 24d520882e
Don't cache transcoded files if the request was cancelled (#2041)
* Don't cache transcoded files if the request was cancelled (or there was a transcoding error)

* Add context to logs

* Simplify Wait error handling

* Fix flaky test

* Change log level for "populating cache" error message

* Small cleanups
2022-12-18 12:22:12 -05:00

87 lines
1.8 KiB
Go

package transcoder
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
"github.com/navidrome/navidrome/log"
)
type Transcoder interface {
Start(ctx context.Context, command, path string, maxBitRate int) (io.ReadCloser, error)
}
func New() Transcoder {
return &externalTranscoder{}
}
type externalTranscoder struct{}
func (e *externalTranscoder) Start(ctx context.Context, command, path string, maxBitRate int) (io.ReadCloser, error) {
args := createTranscodeCommand(command, path, maxBitRate)
log.Trace(ctx, "Executing transcoding command", "cmd", args)
j := &Cmd{ctx: ctx, args: args}
j.PipeReader, j.out = io.Pipe()
err := j.start()
if err != nil {
return nil, err
}
go j.wait()
return j, nil
}
type Cmd struct {
*io.PipeReader
out *io.PipeWriter
ctx context.Context
args []string
cmd *exec.Cmd
}
func (j *Cmd) start() error {
cmd := exec.CommandContext(j.ctx, j.args[0], j.args[1:]...) // #nosec
cmd.Stdout = j.out
cmd.Stderr = os.Stderr
j.cmd = cmd
if err := cmd.Start(); err != nil {
return fmt.Errorf("starting cmd: %w", err)
}
return nil
}
func (j *Cmd) wait() {
if err := j.cmd.Wait(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
_ = j.out.CloseWithError(fmt.Errorf("%s exited with non-zero status code: %d", j.args[0], exitErr.ExitCode()))
} else {
_ = j.out.CloseWithError(fmt.Errorf("waiting %s cmd: %w", j.args[0], err))
}
return
}
if j.ctx.Err() != nil {
_ = j.out.CloseWithError(j.ctx.Err())
return
}
_ = j.out.Close()
}
// Path will always be an absolute path
func createTranscodeCommand(cmd, path string, maxBitRate int) []string {
split := strings.Split(cmd, " ")
for i, s := range split {
s = strings.ReplaceAll(s, "%s", path)
s = strings.ReplaceAll(s, "%b", strconv.Itoa(maxBitRate))
split[i] = s
}
return split
}