Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,11 @@ CLI 配置文件搜索顺序(优先级从高到低):
| `RFFMPEG_MAX_RETRIES` | WS/HTTP 重试次数上限(连接中断后) | `14`(约 5 分钟) |
| `RFFMPEG_SERVER_LOSS_TIMEOUT` | 已提交任务等待期间 Server 断连的快速失败预算(`0` = 关闭该预算) | `5s` |
| `RFFMPEG_POLL_TIMEOUT` | 等待 Worker(pending)阶段的最长轮询时长(`0` = 不设上限) | `10m` |
| `RFFMPEG_LOCAL_FALLBACK` | 允许能力查询(`-encoders`/`-decoders`/`-codecs`/`-hwaccels`/`-filters`/`-pix_fmts`/`-formats`)在 Server 无法应答时改列本地 ffmpeg 能力(`1`/`true` 开启,并打印 `[rffmpeg] local fallback:` 告警行) | 未设置(Server 无法应答即失败退出) |
| `RFFMPEG_LOG_FILE` | 提交前失败诊断日志路径(追加写入,未设置则禁用) | - |

**能力查询在 Server 无法应答时失败退出**:`-encoders`/`-decoders`/`-codecs`/`-hwaccels`/`-filters`/`-pix_fmts`/`-formats` 回答的是**集群**(Worker)的能力,与 `-buildconf`/`-layouts` 等本地查询标志定位不同。因此当 Server 不可达、令牌错误或返回错误时,CLI 打印错误并以非零码退出,**不会静默改用本地 ffmpeg 的能力列表**——否则用户会把本机能力误当成集群能力(例如据此选定一个集群并不支持的编码器)。确需本机列表时:直接运行 `ffmpeg -encoders` 等本地命令,或设置 `RFFMPEG_LOCAL_FALLBACK=1` 显式开启回退(回退时会打印 `[rffmpeg] local fallback: …; listing LOCAL ffmpeg capabilities` 告警行)。

**未配置 Server URL 时的连接失败提示**:`--server`、`RFFMPEG_SERVER_URL` 与配置文件都没有提供 Server URL 时,CLI 使用默认 `http://localhost:8080`;此时若健康检查失败,除错误信息外会额外打印一行提示(说明当前用的是默认 URL,并提示设置 `RFFMPEG_SERVER_URL` 或 `--server`),避免首次使用只看到 `connection refused` 而无从判断。显式指定过 URL(命令行、环境变量、配置文件任一)时不打印该提示。

**提交前失败诊断日志(`RFFMPEG_LOG_FILE`)**:设置该变量指向一个可写文件后,CLI 会在「提交作业之前」以非零退出码退出时,把一条诊断记录追加写入该文件——包含时间戳、进程 PID、退出码、Server URL、命令行(令牌已脱敏)以及本次运行捕获的 stderr 内容。这用于诊断「CLI 在提交前即退出、Server 与 Worker 均无对应 job 记录」的偶发故障:此类故障无服务端线索,仅靠 CLI 侧的 stderr 才能定位。默认不设置(零开销、不改动 stderr 与文件系统),仅当需要诊断时按需开启。日志文件以 `0600` 权限创建,避免令牌(若未来扩展写入)被其他用户读取。
Expand Down
136 changes: 88 additions & 48 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1734,9 +1734,30 @@ func runProbe(cli *client.Client, input string, quiet bool, sharedFS bool) int {
return runProbeRequest(cli, fileID, quiet)
}

// localFallbackEnv opts into answering a server-backed capability query from
// local ffmpeg when the server cannot answer it (see localFallbackAllowed).
const localFallbackEnv = "RFFMPEG_LOCAL_FALLBACK"

// localFallbackAllowed decides what a server-backed capability query does after
// the server failed to answer it. The capability list describes the cluster the
// job will run on, so answering with the local machine's list is a substitution
// the caller must ask for: without the opt-in the query fails, and with it the
// substitution is announced on stderr. Returns true when the caller should run
// local ffmpeg.
func localFallbackAllowed(flag string, serverErr error) bool {
switch os.Getenv(localFallbackEnv) {
case "1", "true":
fmt.Fprintf(os.Stderr, "[rffmpeg] local fallback: %s server query failed (%v); listing LOCAL ffmpeg capabilities\n", flag, serverErr)
return true
}
fmt.Fprintf(os.Stderr, "Error: %s server query failed: %v\n", flag, serverErr)
fmt.Fprintf(os.Stderr, "Hint: this list comes from the cluster; set %s=1 to list the LOCAL ffmpeg's capabilities instead.\n", localFallbackEnv)
return false
}

// runEncoders queries encoders from the server and prints in ffmpeg-compatible format.
// When jsonOut is true, outputs JSON instead of text.
// Falls back to local ffmpeg if server is unavailable.
// Falls back to local ffmpeg only with the localFallbackEnv opt-in.
func runEncoders(serverURL, token string, jsonOut bool) int {
cli, err := setupClient(serverURL, token)
if err == nil {
Expand All @@ -1756,8 +1777,11 @@ func runEncoders(serverURL, token string, jsonOut bool) int {
}
return ExitSuccess
}
err = apiErr
}
if !localFallbackAllowed("-encoders", err) {
return ExitError
}
// Server unavailable — fall back to local ffmpeg
if !jsonOut {
return runLocalFfmpegPassthrough("-encoders")
}
Expand All @@ -1772,7 +1796,7 @@ func runEncoders(serverURL, token string, jsonOut bool) int {

// runDecoders queries decoders from the server and prints in ffmpeg-compatible format.
// When jsonOut is true, outputs JSON instead of text.
// Falls back to local ffmpeg if server is unavailable.
// Falls back to local ffmpeg only with the localFallbackEnv opt-in.
func runDecoders(serverURL, token string, jsonOut bool) int {
cli, err := setupClient(serverURL, token)
if err == nil {
Expand All @@ -1792,8 +1816,11 @@ func runDecoders(serverURL, token string, jsonOut bool) int {
}
return ExitSuccess
}
err = apiErr
}
if !localFallbackAllowed("-decoders", err) {
return ExitError
}
// Server unavailable — fall back to local ffmpeg
if !jsonOut {
return runLocalFfmpegPassthrough("-decoders")
}
Expand All @@ -1818,52 +1845,46 @@ func runDecoders(serverURL, token string, jsonOut bool) int {
// runCodecsFromServer queries both encoders and decoders from the server and prints in
// ffmpeg-compatible format: encoder list first, then decoder list.
// When jsonOut is true, outputs JSON instead of text.
// Falls back to local ffmpeg if server is unavailable.
// Falls back to local ffmpeg only with the localFallbackEnv opt-in.
func runCodecsFromServer(serverURL, token string, jsonOut bool) int {
cli, err := setupClient(serverURL, token)
if err == nil {
if jsonOut {
encoders, apiErr1 := cli.ListAllEncoders()
if apiErr1 != nil {
goto fallback
}
decoders, apiErr2 := cli.ListAllDecoders()
if apiErr2 != nil {
goto fallback
}
return outputInfoFlagJSON("codecs", encoders, decoders)
var encoders []protocol.EncoderInfo
var decoders []protocol.DecoderInfo
encoders, err = cli.ListAllEncoders()
if err == nil {
decoders, err = cli.ListAllDecoders()
}

encoders, apiErr := cli.ListAllEncoders()
if apiErr == nil {
decoders, apiErr2 := cli.ListAllDecoders()
if apiErr2 == nil {
printCodecsHeader("Encoders")
if len(encoders) > 0 {
for _, enc := range encoders {
capFlags := encoderCapabilityFlags(enc.Type)
fmt.Printf("%s %-22s %s\n", capFlags, enc.Name, enc.Description)
}
} else {
fmt.Fprintf(os.Stderr, "No encoders available\n")
if err == nil {
if jsonOut {
return outputInfoFlagJSON("codecs", encoders, decoders)
}
printCodecsHeader("Encoders")
if len(encoders) > 0 {
for _, enc := range encoders {
capFlags := encoderCapabilityFlags(enc.Type)
fmt.Printf("%s %-22s %s\n", capFlags, enc.Name, enc.Description)
}
} else {
fmt.Fprintf(os.Stderr, "No encoders available\n")
}

printCodecsHeader("Decoders")
if len(decoders) > 0 {
for _, dec := range decoders {
capFlags := decoderCapabilityFlags(dec.Type)
fmt.Printf("%s %-22s %s\n", capFlags, dec.Name, dec.Description)
}
} else {
fmt.Fprintf(os.Stderr, "No decoders available\n")
printCodecsHeader("Decoders")
if len(decoders) > 0 {
for _, dec := range decoders {
capFlags := decoderCapabilityFlags(dec.Type)
fmt.Printf("%s %-22s %s\n", capFlags, dec.Name, dec.Description)
}
return ExitSuccess
} else {
fmt.Fprintf(os.Stderr, "No decoders available\n")
}
return ExitSuccess
}
}

fallback:
// Server unavailable — fall back to local ffmpeg
if !localFallbackAllowed("-codecs", err) {
return ExitError
}
if !jsonOut {
return runLocalFfmpegPassthrough("-codecs")
}
Expand Down Expand Up @@ -1904,7 +1925,8 @@ func runLocalFfmpegInfo(flag string) int {
return ExitSuccess
}

// === Local ffmpeg fallback helpers for P0/P1 info flags ===
// === Local ffmpeg fallback helpers for P0/P1 info flags (opt-in, see
// localFallbackAllowed) ===

// runLocalFfmpegOutput runs a local ffmpeg with the given flag, capturing stdout
// and discarding stderr. Returns the combined output string.
Expand Down Expand Up @@ -2128,7 +2150,7 @@ func jsonEncodeToStdout(enc *json.Encoder, v interface{}) int {

// runHwaccels queries hwaccels from the server and prints in ffmpeg-compatible format (single-column list).
// When jsonOut is true, outputs JSON instead of text.
// Falls back to local ffmpeg if server is unavailable.
// Falls back to local ffmpeg only with the localFallbackEnv opt-in.
func runHwaccels(serverURL, token string, jsonOut bool) int {
cli, err := setupClient(serverURL, token)
if err == nil {
Expand All @@ -2146,8 +2168,11 @@ func runHwaccels(serverURL, token string, jsonOut bool) int {
}
return ExitSuccess
}
err = apiErr
}
if !localFallbackAllowed("-hwaccels", err) {
return ExitError
}
// Server unavailable — fall back to local ffmpeg
if !jsonOut {
return runLocalFfmpegPassthrough("-hwaccels")
}
Expand All @@ -2161,7 +2186,7 @@ func runHwaccels(serverURL, token string, jsonOut bool) int {
}

// runFilters queries filters from the server and prints in ffmpeg-compatible format.
// Falls back to local ffmpeg if server is unavailable.
// Falls back to local ffmpeg only with the localFallbackEnv opt-in.
func runFilters(serverURL, token string, jsonOut bool) int {
cli, err := setupClient(serverURL, token)
if err == nil {
Expand All @@ -2180,8 +2205,11 @@ func runFilters(serverURL, token string, jsonOut bool) int {
}
return ExitSuccess
}
err = apiErr
}
if !localFallbackAllowed("-filters", err) {
return ExitError
}
// Server unavailable — fall back to local ffmpeg
if !jsonOut {
return runLocalFfmpegPassthrough("-filters")
}
Expand All @@ -2195,7 +2223,7 @@ func runFilters(serverURL, token string, jsonOut bool) int {
}

// runPixFmts queries pixel formats from the server and prints in ffmpeg-compatible format.
// Falls back to local ffmpeg if server is unavailable.
// Falls back to local ffmpeg only with the localFallbackEnv opt-in.
func runPixFmts(serverURL, token string, jsonOut bool) int {
cli, err := setupClient(serverURL, token)
if err == nil {
Expand All @@ -2214,8 +2242,11 @@ func runPixFmts(serverURL, token string, jsonOut bool) int {
}
return ExitSuccess
}
err = apiErr
}
if !localFallbackAllowed("-pix_fmts", err) {
return ExitError
}
// Server unavailable — fall back to local ffmpeg
if !jsonOut {
return runLocalFfmpegPassthrough("-pix_fmts")
}
Expand All @@ -2229,7 +2260,7 @@ func runPixFmts(serverURL, token string, jsonOut bool) int {
}

// runFormats queries formats from the server and prints in ffmpeg-compatible format.
// Falls back to local ffmpeg if server is unavailable.
// Falls back to local ffmpeg only with the localFallbackEnv opt-in.
func runFormats(serverURL, token string, jsonOut bool) int {
cli, err := setupClient(serverURL, token)
if err == nil {
Expand All @@ -2248,8 +2279,11 @@ func runFormats(serverURL, token string, jsonOut bool) int {
}
return ExitSuccess
}
err = apiErr
}
if !localFallbackAllowed("-formats", err) {
return ExitError
}
// Server unavailable — fall back to local ffmpeg
if !jsonOut {
return runLocalFfmpegPassthrough("-formats")
}
Expand Down Expand Up @@ -2394,6 +2428,10 @@ rffmpeg options:
-filters List all available filters (queries server, no input file required)
-pix_fmts List all available pixel formats (queries server, no input file required)
-formats List all available muxers/demuxers (queries server, no input file required)
Capability queries (-encoders/-decoders/-codecs/-hwaccels/-filters/-pix_fmts/-formats)
describe the cluster, so they fail when the server cannot answer instead of
silently listing this machine's ffmpeg; RFFMPEG_LOCAL_FALLBACK=1 opts into the
local list (announced on stderr).
-buildconf Show build configuration (runs local ffmpeg)
-layouts List channel layouts (runs local ffmpeg)
-protocols List protocols (runs local ffmpeg)
Expand Down Expand Up @@ -2482,6 +2520,8 @@ Configuration:
RFFMPEG_SERVER_URL Server URL
RFFMPEG_TOKEN Auth token
RFFMPEG_SHARED_FS Enable shared filesystem mode (set to 1 or true)
RFFMPEG_LOCAL_FALLBACK Allow capability queries to answer from local ffmpeg
when the server cannot (set to 1 or true; default: fail)

`)
}
Loading
Loading