feat(ffmpeg): Add -ffmpeg flag for custom path

- Add -ffmpeg flag for custom path
- Allow FFMPEG_BIN env var to set path
- Update ffmpeg
This commit is contained in:
spkprsnts
2026-05-11 03:31:14 +05:00
parent 7be008b99e
commit ba3d622233
3 changed files with 46 additions and 10 deletions

View File

@@ -18,6 +18,7 @@ import (
"github.com/openlibrecommunity/olcrtc/internal/app/session"
"github.com/openlibrecommunity/olcrtc/internal/logger"
"github.com/openlibrecommunity/olcrtc/internal/names"
"github.com/openlibrecommunity/olcrtc/internal/transport/videochannel"
)
const modeGen = "gen"
@@ -25,8 +26,10 @@ const modeGen = "gen"
// ErrDataDirRequired is returned when no data directory is specified.
var ErrDataDirRequired = errors.New("data directory required (use -data data)")
//nolint:gochecknoglobals // Tests replace the long-running session runner with a bounded function.
var runSession = session.Run
//nolint:gochecknoglobals // Tests replace gen runner with a stub.
var runGen = execGen
type config struct {
@@ -63,6 +66,7 @@ type config struct {
seiFragmentSize int
seiAckTimeoutMS int
amount int
ffmpegPath string
}
func main() {
@@ -89,6 +93,10 @@ func runWithArgs(args []string) error {
func runWithConfig(cfg config) error {
configureLogging(cfg.debug)
if cfg.ffmpegPath != "ffmpeg" && cfg.ffmpegPath != "" {
videochannel.FFmpegPath = cfg.ffmpegPath
}
if cfg.mode == modeGen {
return runGen(cfg)
}
@@ -200,6 +208,7 @@ func parseFlagsFrom(args []string, errorHandling flag.ErrorHandling) (config, er
fs.IntVar(&cfg.seiFragmentSize, "frag", 0, "Fragment size in bytes for fragmented transports (seichannel)")
fs.IntVar(&cfg.seiAckTimeoutMS, "ack-ms", 0, "ACK timeout in milliseconds for reliable visual transports (seichannel)")
fs.IntVar(&cfg.amount, "amount", 0, "Number of rooms to generate (gen mode only)")
fs.StringVar(&cfg.ffmpegPath, "ffmpeg", "ffmpeg", "Path to ffmpeg executable")
if err := fs.Parse(args); err != nil {
return cfg, fmt.Errorf("parse flags: %w", err)

View File

@@ -147,6 +147,7 @@
| `-video-qr-size` | Размер фрагмента QR в байтах, `0` = авто | `0` |
| `-video-tile-module` | Размер тайла в пикселях 1..270 (только `tile`) | `4` |
| `-video-tile-rs` | Reed-Solomon паритет % 0..200 (только `tile`) | `20` |
| `-ffmpeg` | Путь к исполняемому файлу ffmpeg | `ffmpeg` |
Для codec `tile` нужно точно `1080x1080`.

View File

@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
@@ -42,6 +43,9 @@ var (
ErrUnexpectedFrameSize = errors.New("unexpected encoder frame size")
)
// FFmpegPath defines the path to the ffmpeg executable.
var FFmpegPath = "ffmpeg"
type codecSpec struct {
mimeType string
fourCC string
@@ -195,14 +199,25 @@ func newFFmpegEncoder(
width, height, fps int,
bitrate, hw string,
) (*ffmpegEncoder, error) {
if _, err := exec.LookPath("ffmpeg"); err != nil {
return nil, ErrFFmpegUnavailable
ffmpegBin := FFmpegPath
if envBin := os.Getenv("FFMPEG_BIN"); envBin != "" {
ffmpegBin = envBin
}
if ffmpegBin == "ffmpeg" {
if _, err := exec.LookPath("ffmpeg"); err != nil {
return nil, ErrFFmpegUnavailable
}
} else {
if _, err := os.Stat(ffmpegBin); err != nil {
return nil, fmt.Errorf("%w: %v", ErrFFmpegUnavailable, err)
}
}
vcodec := resolveEncoderCodec(spec, hw)
args := buildEncoderArgs(spec, vcodec, width, height, fps, bitrate)
cmd := exec.CommandContext(ctx, "ffmpeg", args...)
cmd := exec.CommandContext(ctx, ffmpegBin, args...) //nolint:gosec
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, fmt.Errorf("encoder stdin: %w", err)
@@ -397,14 +412,25 @@ func newFFmpegDecoder(
width, height, fps int,
hw string,
) (*ffmpegDecoder, error) {
if _, err := exec.LookPath("ffmpeg"); err != nil {
return nil, ErrFFmpegUnavailable
ffmpegBin := FFmpegPath
if envBin := os.Getenv("FFMPEG_BIN"); envBin != "" {
ffmpegBin = envBin
}
if ffmpegBin == "ffmpeg" {
if _, err := exec.LookPath("ffmpeg"); err != nil {
return nil, ErrFFmpegUnavailable
}
} else {
if _, err := os.Stat(ffmpegBin); err != nil {
return nil, fmt.Errorf("%w: %v", ErrFFmpegUnavailable, err)
}
}
decoderName := resolveDecoderName(spec, hw)
args := buildDecoderArgs(spec, decoderName, width, height, "gray")
cmd := exec.CommandContext(ctx, "ffmpeg", args...)
cmd := exec.CommandContext(ctx, ffmpegBin, args...) //nolint:gosec
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, fmt.Errorf("decoder stdin: %w", err)
@@ -539,9 +565,9 @@ func writeIVFHeader(w io.Writer, fourCC string, width, height, frameRate int) er
binary.LittleEndian.PutUint16(header[4:6], 0)
binary.LittleEndian.PutUint16(header[6:8], 32)
copy(header[8:12], []byte(fourCC))
binary.LittleEndian.PutUint16(header[12:14], uint16(width))
binary.LittleEndian.PutUint16(header[14:16], uint16(height))
binary.LittleEndian.PutUint32(header[16:20], uint32(frameRate))
binary.LittleEndian.PutUint16(header[12:14], uint16(width)) //nolint:gosec
binary.LittleEndian.PutUint16(header[14:16], uint16(height)) //nolint:gosec
binary.LittleEndian.PutUint32(header[16:20], uint32(frameRate)) //nolint:gosec
binary.LittleEndian.PutUint32(header[20:24], 1)
binary.LittleEndian.PutUint32(header[24:28], 0)
binary.LittleEndian.PutUint32(header[28:32], 0)
@@ -550,7 +576,7 @@ func writeIVFHeader(w io.Writer, fourCC string, width, height, frameRate int) er
func writeIVFFrame(w io.Writer, pts uint64, frame []byte) error {
header := make([]byte, 12)
binary.LittleEndian.PutUint32(header[0:4], uint32(len(frame)))
binary.LittleEndian.PutUint32(header[0:4], uint32(len(frame))) //nolint:gosec
binary.LittleEndian.PutUint64(header[4:12], pts)
if err := writeAll(w, header); err != nil {
return err