Skip to content

[cli] Add --stdin to read the input video from a pipe - #598

Open
EnesSoydan wants to merge 3 commits into
Breakthrough:mainfrom
EnesSoydan:fix/434-stdin-input
Open

EnesSoydan wants to merge 3 commits into
Breakthrough:mainfrom
EnesSoydan:fix/434-stdin-input

Conversation

@EnesSoydan

Copy link
Copy Markdown

Closes #434
Closes #189

Adds a --stdin flag, so a video can be piped in:

ffmpeg -i input.mp4 -c copy -f matroska - | scenedetect --stdin detect-content list-scenes

This went through the task list in #189:

  • Backend: it uses VideoStreamAv, since PyAV already takes a file-like object. --stdin with another backend is an error, and so is combining it with -i.
  • Seeking: it isn't possible, so these commands now fail with a clear error before anything is processed: split-video, save-images, load-scenes, and time -s. save-html leaves out images.
  • Source path: save-fcp and save-otio are rejected as well, because they write a reference to a source file that doesn't exist here.
  • $VIDEO_NAME: it is set to stdin, so output files get names like stdin-Scenes.csv.

I left split-video from a stream out of this PR.

Two things I ran into while testing:

  • On Windows, sys.stdin.buffer.seekable() returns True for a pipe, and PyAV then fails with OSError: [Errno 22] when it tries to seek. _PipeReader is a small wrapper that reports the stream as non-seekable.
  • An MP4 with its index at the end of the file opens from a pipe but decodes no frames, without raising an error. The CLI now logs a critical message with a remux hint and exits with 1, so a pipeline doesn't treat it as success. __main__.py lets SystemExit through so this isn't logged as an unhandled exception. The existing OpenCV "no frames" path still exits with 0; I didn't change that here.

The new tests in tests/test_cli.py write to the CLI through a real pipe. They include a check that the scenes match the same video opened with -i.

I edited docs/cli.rst by hand instead of committing the output of generate_cli_docs.py, because the generator also changed some unrelated lines.

Tested on Windows with Python 3.10 and PyAV 17. I haven't run it on Linux or macOS, or with a real ffmpeg pipe, since ffmpeg isn't installed on my machine. The tests pipe the file's bytes directly.

@Breakthrough

Breakthrough commented Nov 8, 2026 •

Copy link
Copy Markdown
Owner

Also FYI I'm trying to get v0.7.3. out right now so I'd like to hold this until after the release just to avoid bringing in any last minute changes. Feel free to continue working on this in the meantime though, I'll make sure we get this landed in the version that we ship just after v0.7.3.

Can you also post a quick comment in each of the linked bugs in the PR description so I can assign those issues to you (so folks know it's taken)? Just something like "I'd like to take this on, staged #598 for this" is sufficient. Thanks.

@Breakthrough Breakthrough left a comment •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, this will be cool to see added! See my comment above for requested changes.

v0.7.3 just shipped, so you can rebase this on main now so we can also run the tests after. Thanks.

@EnesSoydan

Copy link
Copy Markdown
Author

Rebased on main. The only conflict was in the changelog, so the --stdin entry is now under 0.8. I also commented on #434 and #189 as you asked.

@Breakthrough Breakthrough left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v0.7.3 just shipped, so you can rebase this on main now so we can also run the tests after. Thanks. PTAL the review comments as well.

@Breakthrough Breakthrough left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(sorry forgot to post the comments on prev. reply)

Comment thread scenedetect/_cli/controller.py Outdated
" as an MP4 with its index at the end of the file. Try remuxing to MKV or MPEG-TS:\n"
" ffmpeg -i input.mp4 -c copy -f matroska -\n"
)
raise SystemExit(1)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could _detect() return None here like the OpenCV branch above, and have run_scenedetect() report the failure so main() exits with 1? Then the except SystemExit in __main__.py isn't needed.

Comment thread scenedetect/_cli/__init__.py Outdated
"--stdin",
is_flag=True,
default=False,
help="Read the input video from standard input instead of -i/--input. Requires the pyav backend and a container that can be read from a pipe (e.g. MKV or MPEG-TS). Commands that need to seek in the video are not supported.",

@Breakthrough Breakthrough Nov 13, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: the container that can be read part, can we make it more clear how this can be created? e.g. can we reword this to say the MP4 needs its index at the start of the file (ffmpeg -movflags +faststart)? Otherwise, remux to MKV or MPEG-TS? Same text in docs/cli.rst.

Comment thread scenedetect/_cli/__init__.py Outdated
def check_not_stdin(ctx: CliContext, command: str) -> None:
"""Rejects `command` if the input is read from stdin, which cannot seek and has no path."""
if ctx.use_stdin:
raise click.BadParameter(f"{command} is incompatible with --stdin.", param_hint=command)

@Breakthrough Breakthrough Nov 13, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be click.UsageError(f"{command} is incompatible with --stdin."), the current message repeats the command name in the logged output via the param_hint.

Comment thread scenedetect/_cli/__init__.py Outdated
# Make sure a save-images command is in the pipeline for us to use the results from if we need
# to include images.
include_images = not ctx.config.get_value("save-html", "no-images", no_images)
no_images = ctx.config.get_value("save-html", "no-images", no_images) or ctx.use_stdin

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you log a message when --stdin forces no_images?

Comment thread scenedetect/_cli/context.py Outdated
"--stdin requires the pyav backend. Install it with `pip install av`.",
param_hint="--stdin",
)
if sys.stdin.isatty():

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be sys.stdin is None or sys.stdin.isatty()? sys.stdin is None under pythonw.exe on my Windows computer.

Comment thread tests/test_cli.py
assert "Processed" not in output


def test_cli_stdin_unreadable_container():

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment that makes it clear this relies on testvideo.mp4 having its moov atom at the end.

- Return None from _detect() when no frames are read from stdin, and exit
  with 1 from main() when run_scenedetect() reports a failure
- Explain in the --stdin help that an MP4 needs its index at the start
  of the file (-movflags +faststart), otherwise remux to MKV or MPEG-TS
- Raise UsageError for commands that are incompatible with --stdin
- Warn when save-html leaves out images because of --stdin
- Handle sys.stdin being None (e.g. under pythonw.exe)
- Add a CONCORDANCE row for --stdin and regenerate docs/cli.rst
- Fix pyright errors in _open_video_stream
@EnesSoydan

Copy link
Copy Markdown
Author

Thanks for the review! Pushed 97e01e6:

  • _detect() now returns None for stdin as well, and main() exits with 1 when run_scenedetect() reports a failure, so the except SystemExit is gone. This also makes the OpenCV "no frames" and failed seek paths exit with 1 instead of 0, so I added a changelog line for that. Happy to limit it to stdin if you'd rather keep those as they are.
  • Reworded the --stdin help (and docs/cli.rst) to mention -movflags +faststart. I checked that a faststart remux of testvideo.mp4 reads fine from a pipe.
  • UsageError for incompatible commands, a warning when save-html drops images, sys.stdin is None, and the test comment, as suggested.
  • For CI: added a CONCORDANCE row for --stdin and regenerated docs/cli.rst, and fixed the pyright errors in _open_video_stream.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support of input type "std input" [Feature Request] Support input via pipe

2 participants