2019-02-09 12:30:49 +00:00
|
|
|
package ffmpeg
|
|
|
|
|
|
|
|
import (
|
2021-05-05 03:22:05 +00:00
|
|
|
"bytes"
|
2021-09-27 00:55:23 +00:00
|
|
|
"io"
|
2019-07-24 22:17:22 +00:00
|
|
|
"os"
|
2019-02-09 12:30:49 +00:00
|
|
|
"os/exec"
|
2019-03-28 23:29:45 +00:00
|
|
|
"strings"
|
2019-11-14 18:27:13 +00:00
|
|
|
"sync"
|
2019-10-17 20:42:12 +00:00
|
|
|
"time"
|
2019-07-24 22:17:22 +00:00
|
|
|
|
|
|
|
"github.com/stashapp/stash/pkg/logger"
|
2019-02-09 12:30:49 +00:00
|
|
|
)
|
|
|
|
|
2021-10-14 23:39:48 +00:00
|
|
|
type Encoder string
|
2019-02-09 12:30:49 +00:00
|
|
|
|
2019-11-14 18:27:13 +00:00
|
|
|
var (
|
2020-04-29 02:13:08 +00:00
|
|
|
runningEncoders = make(map[string][]*os.Process)
|
|
|
|
runningEncodersMutex = sync.RWMutex{}
|
2019-11-14 18:27:13 +00:00
|
|
|
)
|
2019-10-17 20:42:12 +00:00
|
|
|
|
|
|
|
func registerRunningEncoder(path string, process *os.Process) {
|
2019-11-14 18:27:13 +00:00
|
|
|
runningEncodersMutex.Lock()
|
2019-10-17 20:42:12 +00:00
|
|
|
processes := runningEncoders[path]
|
|
|
|
|
|
|
|
runningEncoders[path] = append(processes, process)
|
2019-11-14 18:27:13 +00:00
|
|
|
runningEncodersMutex.Unlock()
|
2019-10-17 20:42:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func deregisterRunningEncoder(path string, process *os.Process) {
|
2019-11-14 18:27:13 +00:00
|
|
|
runningEncodersMutex.Lock()
|
|
|
|
defer runningEncodersMutex.Unlock()
|
2019-10-17 20:42:12 +00:00
|
|
|
processes := runningEncoders[path]
|
|
|
|
|
|
|
|
for i, v := range processes {
|
|
|
|
if v == process {
|
|
|
|
runningEncoders[path] = append(processes[:i], processes[i+1:]...)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func waitAndDeregister(path string, cmd *exec.Cmd) error {
|
|
|
|
err := cmd.Wait()
|
|
|
|
deregisterRunningEncoder(path, cmd.Process)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func KillRunningEncoders(path string) {
|
2019-11-14 18:27:13 +00:00
|
|
|
runningEncodersMutex.RLock()
|
2019-10-17 20:42:12 +00:00
|
|
|
processes := runningEncoders[path]
|
2019-11-14 18:27:13 +00:00
|
|
|
runningEncodersMutex.RUnlock()
|
2019-10-17 20:42:12 +00:00
|
|
|
|
|
|
|
for _, process := range processes {
|
|
|
|
// assume it worked, don't check for error
|
2021-05-05 03:22:05 +00:00
|
|
|
logger.Infof("Killing encoder process for file: %s", path)
|
2021-09-23 07:15:50 +00:00
|
|
|
if err := process.Kill(); err != nil {
|
|
|
|
logger.Warnf("failed to kill process %v: %v", process.Pid, err)
|
|
|
|
}
|
2019-10-17 20:42:12 +00:00
|
|
|
|
|
|
|
// wait for the process to die before returning
|
|
|
|
// don't wait more than a few seconds
|
|
|
|
done := make(chan error)
|
2019-11-04 21:34:57 +00:00
|
|
|
go func() {
|
|
|
|
_, err := process.Wait()
|
2019-10-17 20:42:12 +00:00
|
|
|
done <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
return
|
|
|
|
case <-time.After(5 * time.Second):
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 03:22:05 +00:00
|
|
|
// FFmpeg runner with progress output, used for transcodes
|
|
|
|
func (e *Encoder) runTranscode(probeResult VideoFile, args []string) (string, error) {
|
2021-10-14 23:39:48 +00:00
|
|
|
cmd := exec.Command(string(*e), args...)
|
2019-02-09 12:30:49 +00:00
|
|
|
|
|
|
|
stderr, err := cmd.StderrPipe()
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("FFMPEG stderr not available: " + err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
if nil != err {
|
|
|
|
logger.Error("FFMPEG stdout not available: " + err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = cmd.Start(); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := make([]byte, 80)
|
2020-08-21 07:57:07 +00:00
|
|
|
lastProgress := 0.0
|
2019-11-04 21:34:57 +00:00
|
|
|
var errBuilder strings.Builder
|
2019-02-09 12:30:49 +00:00
|
|
|
for {
|
|
|
|
n, err := stderr.Read(buf)
|
|
|
|
if n > 0 {
|
|
|
|
data := string(buf[0:n])
|
2019-03-28 22:58:13 +00:00
|
|
|
time := GetTimeFromRegex(data)
|
|
|
|
if time > 0 && probeResult.Duration > 0 {
|
2019-02-09 12:30:49 +00:00
|
|
|
progress := time / probeResult.Duration
|
2020-08-21 07:57:07 +00:00
|
|
|
|
|
|
|
if progress > lastProgress+0.01 {
|
|
|
|
logger.Infof("Progress %.2f", progress)
|
|
|
|
lastProgress = progress
|
|
|
|
}
|
2019-02-09 12:30:49 +00:00
|
|
|
}
|
2019-11-04 21:34:57 +00:00
|
|
|
|
|
|
|
errBuilder.WriteString(data)
|
2019-02-09 12:30:49 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-27 00:55:23 +00:00
|
|
|
stdoutData, _ := io.ReadAll(stdout)
|
2019-02-09 12:30:49 +00:00
|
|
|
stdoutString := string(stdoutData)
|
|
|
|
|
2019-10-17 20:42:12 +00:00
|
|
|
registerRunningEncoder(probeResult.Path, cmd.Process)
|
|
|
|
err = waitAndDeregister(probeResult.Path, cmd)
|
|
|
|
|
|
|
|
if err != nil {
|
2019-11-04 21:34:57 +00:00
|
|
|
// error message should be in the stderr stream
|
|
|
|
logger.Errorf("ffmpeg error when running command <%s>: %s", strings.Join(cmd.Args, " "), errBuilder.String())
|
2019-02-09 12:30:49 +00:00
|
|
|
return stdoutString, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return stdoutString, nil
|
2019-02-14 22:53:32 +00:00
|
|
|
}
|
2021-05-05 03:22:05 +00:00
|
|
|
|
2021-10-14 23:39:48 +00:00
|
|
|
func (e *Encoder) run(sourcePath string, args []string, stdin io.Reader) (string, error) {
|
|
|
|
cmd := exec.Command(string(*e), args...)
|
2021-05-05 03:22:05 +00:00
|
|
|
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
cmd.Stdout = &stdout
|
|
|
|
cmd.Stderr = &stderr
|
2021-10-14 23:39:48 +00:00
|
|
|
cmd.Stdin = stdin
|
2021-05-05 03:22:05 +00:00
|
|
|
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2021-10-14 23:39:48 +00:00
|
|
|
var err error
|
|
|
|
if sourcePath != "" {
|
|
|
|
registerRunningEncoder(sourcePath, cmd.Process)
|
|
|
|
err = waitAndDeregister(sourcePath, cmd)
|
|
|
|
} else {
|
|
|
|
err = cmd.Wait()
|
|
|
|
}
|
2021-05-05 03:22:05 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
// error message should be in the stderr stream
|
|
|
|
logger.Errorf("ffmpeg error when running command <%s>: %s", strings.Join(cmd.Args, " "), stderr.String())
|
|
|
|
return stdout.String(), err
|
|
|
|
}
|
|
|
|
|
|
|
|
return stdout.String(), nil
|
|
|
|
}
|