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
13 changes: 12 additions & 1 deletion backend/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ def _has_successful_results(results: list) -> bool:

def _should_enter_post_render_loop(config: dict, interrupted: bool, results: list) -> bool:
"""Open the post-render rerender flow on explicit config or partial interrupt recovery."""
# The post-render loop is interactive (prompt_toolkit); never enter it
# without a real terminal or it crashes with OSError [Errno 22].
if not sys.stdin.isatty():
return False
return bool(config.get("post_render_review", False) or (interrupted and _has_successful_results(results)))


Expand Down Expand Up @@ -669,7 +673,7 @@ def _transcribe_progress(pct, msg):
print(f"\n [4/4] Exporting {len(clips)} clips{_ai_label} to {output_dir}/")
results = []
t0 = time.time()
_skip_review = not config.get("review_each_clip", False)
_skip_review = not config.get("review_each_clip", False) or not sys.stdin.isatty()
interrupted = False

try:
Expand Down Expand Up @@ -1167,6 +1171,13 @@ def _find_moment_with_claude(description: str, segments: list, existing_clips: l

def _review_clips(clips: list, segments: list, energy_scores: list | None, config: dict) -> list:
"""Interactive clip review — user can select/deselect, ask for more, or find specific moments."""
# Non-interactive (piped/scripted/no TTY): skip the picker and render all
# suggested clips. The picker uses prompt_toolkit, which raises
# OSError [Errno 22] when stdin isn't a real terminal.
if not sys.stdin.isatty():
print(f"\n Non-interactive run — rendering all {len(clips)} suggested clips.")
return clips

import questionary
from questionary import Style

Expand Down
32 changes: 22 additions & 10 deletions tests/test_cli_output_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,25 @@ def test_has_successful_results_false_without_outputs(self):
)

def test_should_enter_post_render_loop_when_interrupted_with_completed_results(self):
should_enter = cli_mod._should_enter_post_render_loop(
config={"post_render_review": False},
interrupted=True,
results=[{"output_path": "/tmp/clip.mp4"}],
)
with mock.patch("sys.stdin.isatty", return_value=True):
should_enter = cli_mod._should_enter_post_render_loop(
config={"post_render_review": False},
interrupted=True,
results=[{"output_path": "/tmp/clip.mp4"}],
)

self.assertTrue(should_enter)

def test_should_not_enter_post_render_loop_without_tty(self):
with mock.patch("sys.stdin.isatty", return_value=False):
should_enter = cli_mod._should_enter_post_render_loop(
config={"post_render_review": True},
interrupted=True,
results=[{"output_path": "/tmp/clip.mp4"}],
)

self.assertFalse(should_enter)

def test_should_not_enter_post_render_loop_when_interrupted_without_completed_results(self):
should_enter = cli_mod._should_enter_post_render_loop(
config={"post_render_review": False},
Expand All @@ -93,11 +104,12 @@ def test_should_not_enter_post_render_loop_when_interrupted_without_completed_re
self.assertFalse(should_enter)

def test_should_enter_post_render_loop_when_config_enabled(self):
should_enter = cli_mod._should_enter_post_render_loop(
config={"post_render_review": True},
interrupted=False,
results=[],
)
with mock.patch("sys.stdin.isatty", return_value=True):
should_enter = cli_mod._should_enter_post_render_loop(
config={"post_render_review": True},
interrupted=False,
results=[],
)

self.assertTrue(should_enter)

Expand Down
Loading