mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-04 16:02:43 +08:00
Code refactoring after #878
This commit is contained in:
@@ -412,11 +412,17 @@ The source can be used with:
|
|||||||
- [Raspberry Pi Cameras](https://www.raspberrypi.com/documentation/computers/camera_software.html)
|
- [Raspberry Pi Cameras](https://www.raspberrypi.com/documentation/computers/camera_software.html)
|
||||||
- any your own software
|
- any your own software
|
||||||
|
|
||||||
|
Pipe commands support two parameters (format: `exec:{command}#{param1}#{param2}`):
|
||||||
|
|
||||||
|
- `killsignal` - signal which will be send to stop the process (numeric form)
|
||||||
|
- `killtimeout` - time in seconds for forced termination with sigkill
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
streams:
|
streams:
|
||||||
stream: exec:ffmpeg -re -i /media/BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp {output}
|
stream: exec:ffmpeg -re -i /media/BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp {output}
|
||||||
picam_h264: exec:libcamera-vid -t 0 --inline -o -
|
picam_h264: exec:libcamera-vid -t 0 --inline -o -
|
||||||
picam_mjpeg: exec:libcamera-vid -t 0 --codec mjpeg -o -
|
picam_mjpeg: exec:libcamera-vid -t 0 --codec mjpeg -o -
|
||||||
|
canon: exec:gphoto2 --capture-movie --stdout#killsignal=2#killtimeout=5
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Source: Echo
|
#### Source: Echo
|
||||||
|
@@ -5,8 +5,10 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -20,12 +22,6 @@ import (
|
|||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Params struct {
|
|
||||||
KillSignal os.Signal
|
|
||||||
Command string
|
|
||||||
KillTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
rtsp.HandleFunc(func(conn *pkg.Conn) bool {
|
rtsp.HandleFunc(func(conn *pkg.Conn) bool {
|
||||||
waitersMu.Lock()
|
waitersMu.Lock()
|
||||||
@@ -50,22 +46,19 @@ func Init() {
|
|||||||
log = app.GetLogger("exec")
|
log = app.GetLogger("exec")
|
||||||
}
|
}
|
||||||
|
|
||||||
func execHandle(url string) (core.Producer, error) {
|
func execHandle(rawURL string) (core.Producer, error) {
|
||||||
var path string
|
var path string
|
||||||
|
|
||||||
params, err := parseParams(url)
|
rawURL, rawQuery, _ := strings.Cut(rawURL, "#")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
args := shell.QuoteSplit(params.Command[5:]) // remove `exec:`
|
args := shell.QuoteSplit(rawURL[5:]) // remove `exec:`
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
if arg == "{output}" {
|
if arg == "{output}" {
|
||||||
if rtsp.Port == "" {
|
if rtsp.Port == "" {
|
||||||
return nil, errors.New("rtsp module disabled")
|
return nil, errors.New("rtsp module disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
sum := md5.Sum([]byte(url))
|
sum := md5.Sum([]byte(rawURL))
|
||||||
path = "/" + hex.EncodeToString(sum[:])
|
path = "/" + hex.EncodeToString(sum[:])
|
||||||
args[i] = "rtsp://127.0.0.1:" + rtsp.Port + path
|
args[i] = "rtsp://127.0.0.1:" + rtsp.Port + path
|
||||||
break
|
break
|
||||||
@@ -78,14 +71,15 @@ func execHandle(url string) (core.Producer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return handlePipe(url, cmd, params)
|
query := streams.ParseQuery(rawQuery)
|
||||||
|
return handlePipe(rawURL, cmd, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleRTSP(url, path, cmd)
|
return handleRTSP(rawURL, path, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePipe(_ string, cmd *exec.Cmd, params *Params) (core.Producer, error) {
|
func handlePipe(_ string, cmd *exec.Cmd, query url.Values) (core.Producer, error) {
|
||||||
r, err := PipeCloser(cmd, params)
|
r, err := PipeCloser(cmd, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
package exec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/internal/streams"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseParams(s string) (*Params, error) {
|
|
||||||
args := &Params{
|
|
||||||
Command: s,
|
|
||||||
}
|
|
||||||
|
|
||||||
var query url.Values
|
|
||||||
if i := strings.IndexByte(s, '#'); i > 0 {
|
|
||||||
query = streams.ParseQuery(s[i+1:])
|
|
||||||
args.Command = s[:i]
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := query["killsignal"]; ok {
|
|
||||||
return nil, fmt.Errorf("killsignal is not supported this %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := query["killtimeout"]; ok {
|
|
||||||
return nil, fmt.Errorf("killtimeout is not supported in %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
return args, nil
|
|
||||||
}
|
|
@@ -1,88 +0,0 @@
|
|||||||
package exec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/internal/streams"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseParams(s string) (*Params, error) {
|
|
||||||
args := &Params{
|
|
||||||
KillSignal: syscall.SIGKILL,
|
|
||||||
KillTimeout: 5 * time.Second,
|
|
||||||
Command: s,
|
|
||||||
}
|
|
||||||
|
|
||||||
var query url.Values
|
|
||||||
if i := strings.IndexByte(s, '#'); i > 0 {
|
|
||||||
query = streams.ParseQuery(s[i+1:])
|
|
||||||
args.Command = s[:i]
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, ok := query["killsignal"]; ok {
|
|
||||||
if sig, err := parseSignal(val[0]); err == nil {
|
|
||||||
args.KillSignal = sig
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("could not parse killsignal param (%s)", val[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, ok := query["killtimeout"]; ok {
|
|
||||||
if i, err := strconv.Atoi(val[0]); err == nil {
|
|
||||||
args.KillTimeout = time.Duration(i) * time.Second
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("could not convert killtimeout param (%s) to int", val[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSignal(signalString string) (os.Signal, error) {
|
|
||||||
signalMap := map[string]os.Signal{
|
|
||||||
"sighup": syscall.SIGHUP,
|
|
||||||
"sigint": syscall.SIGINT,
|
|
||||||
"sigquit": syscall.SIGQUIT,
|
|
||||||
"sigill": syscall.SIGILL,
|
|
||||||
"sigtrap": syscall.SIGTRAP,
|
|
||||||
"sigabrt": syscall.SIGABRT,
|
|
||||||
"sigbus": syscall.SIGBUS,
|
|
||||||
"sigfpe": syscall.SIGFPE,
|
|
||||||
"sigkill": syscall.SIGKILL,
|
|
||||||
"sigusr1": syscall.SIGUSR1,
|
|
||||||
"sigsegv": syscall.SIGSEGV,
|
|
||||||
"sigusr2": syscall.SIGUSR2,
|
|
||||||
"sigpipe": syscall.SIGPIPE,
|
|
||||||
"sigalrm": syscall.SIGALRM,
|
|
||||||
"sigterm": syscall.SIGTERM,
|
|
||||||
"sigchld": syscall.SIGCHLD,
|
|
||||||
"sigcont": syscall.SIGCONT,
|
|
||||||
"sigstop": syscall.SIGSTOP,
|
|
||||||
"sigtstp": syscall.SIGTSTP,
|
|
||||||
"sigttin": syscall.SIGTTIN,
|
|
||||||
"sigttou": syscall.SIGTTOU,
|
|
||||||
"sigurg": syscall.SIGURG,
|
|
||||||
"sigxcpu": syscall.SIGXCPU,
|
|
||||||
"sigxfsz": syscall.SIGXFSZ,
|
|
||||||
"sigvtalrm": syscall.SIGVTALRM,
|
|
||||||
"sigprof": syscall.SIGPROF,
|
|
||||||
"sigwinch": syscall.SIGWINCH,
|
|
||||||
"sigio": syscall.SIGIO,
|
|
||||||
"sigpoll": syscall.SIGPOLL,
|
|
||||||
"sigpwr": syscall.SIGPWR,
|
|
||||||
"sigsys": syscall.SIGSYS,
|
|
||||||
}
|
|
||||||
|
|
||||||
signalValue, ok := signalMap[strings.ToLower(signalString)]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid signal: %s", signalString)
|
|
||||||
}
|
|
||||||
|
|
||||||
return signalValue, nil
|
|
||||||
}
|
|
@@ -1,37 +1,56 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
package exec
|
package exec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PipeCloser - return StdoutPipe that Kill cmd on Close call
|
// PipeCloser - return StdoutPipe that Kill cmd on Close call
|
||||||
func PipeCloser(cmd *exec.Cmd, params *Params) (io.ReadCloser, error) {
|
func PipeCloser(cmd *exec.Cmd, query url.Values) (io.ReadCloser, error) {
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// add buffer for pipe reader to reduce syscall
|
// add buffer for pipe reader to reduce syscall
|
||||||
return pipeCloser{bufio.NewReaderSize(stdout, core.BufferSize), stdout, cmd, params}, nil
|
return &pipeCloser{bufio.NewReaderSize(stdout, core.BufferSize), stdout, cmd, query}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type pipeCloser struct {
|
type pipeCloser struct {
|
||||||
io.Reader
|
io.Reader
|
||||||
io.Closer
|
io.Closer
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
params *Params
|
query url.Values
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p pipeCloser) Close() error {
|
func (p *pipeCloser) Close() error {
|
||||||
finished := make(chan bool)
|
return errors.Join(p.Closer.Close(), p.Kill(), p.Wait())
|
||||||
|
}
|
||||||
err := core.Any(p.Closer.Close(), p.cmd.Process.Kill(), p.cmd.Wait())
|
|
||||||
finished <- true
|
func (p *pipeCloser) Kill() error {
|
||||||
return err
|
if s := p.query.Get("killsignal"); s != "" {
|
||||||
|
log.Trace().Msgf("[exec] kill with custom sig=%s", s)
|
||||||
|
sig := syscall.Signal(core.Atoi(s))
|
||||||
|
return p.cmd.Process.Signal(sig)
|
||||||
|
}
|
||||||
|
return p.cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipeCloser) Wait() error {
|
||||||
|
if s := p.query.Get("killtimeout"); s != "" {
|
||||||
|
timeout := time.Duration(core.Atoi(s)) * time.Second
|
||||||
|
timer := time.AfterFunc(timeout, func() {
|
||||||
|
log.Trace().Msgf("[exec] kill after timeout=%s", s)
|
||||||
|
_ = p.cmd.Process.Kill()
|
||||||
|
})
|
||||||
|
defer timer.Stop() // stop timer if Wait ends before timeout
|
||||||
|
}
|
||||||
|
return p.cmd.Wait()
|
||||||
}
|
}
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
package exec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PipeCloser - return StdoutPipe that Kill cmd on Close call
|
|
||||||
func PipeCloser(cmd *exec.Cmd, params *Params) (io.ReadCloser, error) {
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add buffer for pipe reader to reduce syscall
|
|
||||||
return pipeCloser{bufio.NewReaderSize(stdout, core.BufferSize), stdout, cmd, params}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type pipeCloser struct {
|
|
||||||
io.Reader
|
|
||||||
io.Closer
|
|
||||||
cmd *exec.Cmd
|
|
||||||
params *Params
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p pipeCloser) Close() error {
|
|
||||||
finished := make(chan bool)
|
|
||||||
|
|
||||||
if p.params.KillSignal != syscall.SIGKILL {
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-time.After(p.params.KillTimeout):
|
|
||||||
p.cmd.Process.Kill()
|
|
||||||
break
|
|
||||||
case <-finished:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
err := core.Any(p.Closer.Close(), p.cmd.Process.Signal(p.params.KillSignal), p.cmd.Wait())
|
|
||||||
finished <- true
|
|
||||||
return err
|
|
||||||
}
|
|
@@ -6,6 +6,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ParseQuery(s string) url.Values {
|
func ParseQuery(s string) url.Values {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
for _, key := range strings.Split(s, "#") {
|
for _, key := range strings.Split(s, "#") {
|
||||||
var value string
|
var value string
|
||||||
|
Reference in New Issue
Block a user