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: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ ffrwd [<command>] [-h] [-f FILE] [-v NAME=VALUE] [-q] [query | recipe]

| command | what it does | flags |
|---|---|---|
| `run` | **the default**: a query with a media destination compiles and executes it; one without prints its result set as a table, psql-style, executing nothing | `--timeout SECS` (default: ten times the longest input's duration, at least 600, none when an input is live) 路 `-y` (overwrite) 路 `--jobs N` (cap the sidecar's threads) 路 `--profile` (each sidecar's bytes and time per stage, as a row) 路 `--show` / `--show-only` (below) 路 `--remote` (below) |
| `run` | **the default**: a query with a media destination compiles and executes it; one without prints its result set as a table, psql-style, executing nothing | `--timeout SECS` (default: ten times the longest input's duration, at least 600, none when an input is live) 路 `-y` (overwrite) 路 `--jobs N` (cap the sidecar's threads) 路 `--profile` (each sidecar's bytes and time per stage, as a row) 路 `--gpu-adapter SPEC` (the GPU the sidecar works on, by its index in `setup gpu`'s list or part of its name; `FFRWD_GPU_ADAPTER` when absent) 路 `--show` / `--show-only` (below) 路 `--remote` (below) |
| `compile` | print the full ffmpeg command | `--graph-only` (just the filtergraph string) |
| `explain` | dump the compiled graph as JSON | `--mermaid` (as a flowchart) 路 `--diagram` (rendered in the terminal; needs `ffrwd[diagram]`) |
| `validate` | exit 0 if the query compiles, else a line-anchored error | `--json` (machine-readable error object on stdout) |
Expand All @@ -217,6 +217,7 @@ ffrwd [<command>] [-h] [-f FILE] [-v NAME=VALUE] [-q] [query | recipe]
| `publish` | run the package's tests, pack it, and publish it to the registry | |
| `setup` | download everything detection wants on this machine: ffmpeg and ffprobe where PATH has none, and the ONNX Runtime tiers | `--cuda` 路 `--full` as below; `FFRWD_OFFLINE=1` refuses every fetch |
| `setup nn` | download the ONNX Runtime the model-running modules need, the tiers chosen for this machine | `--cuda` 路 `--full` to take a tier detection did not |
| `setup gpu` | list the GPU adapters the sidecar can work on, and the one a run uses; downloads nothing | |
| `jobs` | list, watch, cancel or fetch your remote runs | `--watch` 路 `--cancel ID` 路 `--fetch ID` 路 `-y` 路 `--json` |
| `prompt` | print the LLM system prompt | |
| `mcp` | serve the compiler to an editor or agent over MCP (stdio) | `--allow-unsafe` (also expose the tools that do more than answer about a query) |
Expand Down
94 changes: 91 additions & 3 deletions cli/ffrwd/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@
such block. It exists for the printed ``loudnorm2`` command line, which
pipes pass 1 into it.

``compile`` and ``run`` take ``--gpu-adapter SPEC``, the GPU adapter every
sidecar opens its device on: an index into the list ``ffrwd setup gpu``
prints, or a case-insensitive part of the adapter's name. Absent, it is
`FFRWD_GPU_ADAPTER`'s, and with neither the sidecar opens the adapter it
always has and the command is unchanged. ``setup gpu`` downloads nothing: it
prints the adapters the sidecar lists, and which one a run opens.

``compile``/``explain``/``validate``/``run`` take the query as SQL TEXT on
the command line. ``-f/--file PATH`` reads it from a file instead (``-f -``
reads stdin, e.g. for the LLM repair loop's pipe). Exactly one of the two is
Expand Down Expand Up @@ -196,6 +203,7 @@
binaries,
credentials,
diagram,
gpu,
loudnorm,
nn,
redact,
Expand Down Expand Up @@ -363,7 +371,8 @@ def main(argv: list[str] | None = None) -> int:
# twice (the table-query fallback), and the reader wants each warning once.
warnings = WarningLog()
try:
return handler(args, warnings)
with gpu.chosen(getattr(args, "gpu_adapter", None)):
return handler(args, warnings)
except FfrwdError as err:
# A refusal raised outside a command's own handling -- FFRWD_OFFLINE
# refusing the ffmpeg provider's download wherever ffmpeg is first
Expand Down Expand Up @@ -466,6 +475,35 @@ def _add_profile_argument(subparser: argparse.ArgumentParser) -> None:
subparser.add_argument("--profile", action="store_true", help=_PROFILE_HELP)


_GPU_ADAPTER_HELP = (
f"the GPU adapter every sidecar opens its device on: its index in `{gpu.SETUP_COMMAND}`'s "
f"list, or part of its name (default: ${gpu.ADAPTER_VAR}, else the sidecar's own choice)"
)


def _add_gpu_adapter_argument(subparser: argparse.ArgumentParser) -> None:
subparser.add_argument(
"--gpu-adapter",
default=None,
metavar="SPEC",
dest="gpu_adapter",
help=_GPU_ADAPTER_HELP,
)


def _check_gpu_adapter(args: argparse.Namespace) -> int:
"""0 unless an adapter was named for a remote run, which runs on the
runner's own; 2 with the usage error printed."""
if getattr(args, "gpu_adapter", None) is None or not args.remote:
return 0
print(
f"error: {args.command}: --gpu-adapter names an adapter on this machine",
file=sys.stderr,
)
print("hint: drop --gpu-adapter, or drop --remote", file=sys.stderr)
return 2


def _check_jobs(args: argparse.Namespace) -> int:
"""0 for a usable ``--jobs``, or 2 with the usage error printed."""
jobs: int | None = getattr(args, "jobs", None)
Expand Down Expand Up @@ -559,6 +597,7 @@ def _build_parser() -> argparse.ArgumentParser:
)
_add_jobs_argument(compile_p)
_add_profile_argument(compile_p)
_add_gpu_adapter_argument(compile_p)
explain_p = subparsers.add_parser("explain", help="dump the compiled IR graph as JSON")
_add_query_arguments(explain_p)
_add_quiet_argument(explain_p)
Expand Down Expand Up @@ -605,6 +644,7 @@ def _build_parser() -> argparse.ArgumentParser:
)
_add_jobs_argument(run_p)
_add_profile_argument(run_p)
_add_gpu_adapter_argument(run_p)
showing = run_p.add_mutually_exclusive_group()
showing.add_argument(
"--show",
Expand Down Expand Up @@ -796,9 +836,10 @@ def _build_parser() -> argparse.ArgumentParser:
setup_p.add_argument(
"what",
nargs="?",
choices=["nn"],
choices=["nn", "gpu"],
help="nn: only the ONNX Runtime the modules that run models load; "
"omitted, ffmpeg and ffprobe too, where PATH does not have them",
"omitted, ffmpeg and ffprobe too, where PATH does not have them. gpu: "
"download nothing, and list the GPU adapters a run can name with --gpu-adapter",
)
setup_p.add_argument(
"--cuda",
Expand Down Expand Up @@ -1663,6 +1704,9 @@ def _cmd_run(args: argparse.Namespace, on_warning: OnWarning) -> int:
if code != 0:
return code
code = _check_target(args)
if code != 0:
return code
code = _check_gpu_adapter(args)
if code != 0:
return code
try:
Expand Down Expand Up @@ -3706,6 +3750,15 @@ def _cmd_setup(args: argparse.Namespace, on_warning: OnWarning) -> int:
tier whatever detection said, either way.
"""
console = _console(args)
if args.what == "gpu":
if args.cuda or args.full:
print(
"error: --cuda and --full choose ONNX Runtime libraries; "
"`ffrwd setup gpu` downloads nothing",
file=sys.stderr,
)
return 2
return _setup_gpu()
if args.full and not args.cuda:
print(
"error: --full adds the libraries the CUDA provider needs; pass --cuda too",
Expand All @@ -3717,6 +3770,41 @@ def _cmd_setup(args: argparse.Namespace, on_warning: OnWarning) -> int:
return _setup_all(args, console)


def _setup_gpu() -> int:
"""``ffrwd setup gpu``: the adapters the sidecar lists, and the one a run
opens, which `FFRWD_GPU_ADAPTER` decides where it is set."""
given = gpu.spec()
try:
found = gpu.info(given)
except FfrwdError as err:
_print_error(err)
return 1
if not found.adapters:
print("the sidecar finds no GPU adapter on this machine")
return 0
print(gpu.describe(found))
if given is None:
default = next((one for one in found.adapters if one.default), None)
if default is not None:
print(
f"a run opens {default.index}, {default.name} ({default.backend}); "
f"--gpu-adapter or {gpu.ADAPTER_VAR} names another"
)
return 0
try:
gpu.check()
except FfrwdError as err:
_print_error(err)
return 1
picked = found.adapters[found.chosen] if found.chosen is not None else None
if picked is not None:
print(
f"{gpu.ADAPTER_VAR}={given} names {picked.index}, "
f"{picked.name} ({picked.backend}): a run opens it"
)
return 0


def _setup_command(args: argparse.Namespace) -> str:
"""The ``ffrwd setup`` spelling this invocation is, for a refusal to name."""
words = ["ffrwd", "setup"]
Expand Down
5 changes: 3 additions & 2 deletions cli/ffrwd/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ class ErrorCode(str, Enum):
OUTPUT_EXISTS = "OUTPUT_EXISTS" # a destination file is already there, and no -y
NOTHING_TO_SHOW = "NOTHING_TO_SHOW" # --show/--show-only, no video-bearing output
PLAYER_NOT_FOUND = "PLAYER_NOT_FOUND" # --show asked for, ffplay not on PATH
# setup nn: no ONNX Runtime pinned for this platform; or FFRWD_OFFLINE
# refusing a download ffmpeg or the ONNX Runtime needs
# setup nn: no ONNX Runtime pinned for this platform; FFRWD_OFFLINE
# refusing a download ffmpeg or the ONNX Runtime needs; or a run naming a
# GPU adapter this machine does not have
RUNTIME_NOT_FOUND = "RUNTIME_NOT_FOUND"
UNBOUNDED_LIVE_INPUT = "UNBOUNDED_LIVE_INPUT" # one-open input, uncountable paths
LIVE_LEAD = "LIVE_LEAD" # a live node fed later than the lead it needs
Expand Down
Loading
Loading