Skip to content

feat(gui): add in-app stdout/stderr console with live subprocess streaming#162

Closed
zackees wants to merge 90 commits into
Djdefrag:mainfrom
zackees:feat/gui-console
Closed

feat(gui): add in-app stdout/stderr console with live subprocess streaming#162
zackees wants to merge 90 commits into
Djdefrag:mainfrom
zackees:feat/gui-console

Conversation

@zackees

@zackees zackees commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Closes #62

Summary

  • Adds a collapsible in-app console panel (toggled via a >_ header button) below the CTkTabview that mirrors stdout/stderr from the GUI process and from upscale/frame-gen worker processes.
  • New core/proc.py stream_subprocess() streams subprocess output line-by-line, using the running-process package when available (with explicit stderr=subprocess.PIPE so streams stay separate) and falling back to subprocess.Popen + reader threads otherwise; ffmpeg extraction/encoding in core/media.py now goes through it with cancel + progress-tick support.
  • New gui/console_log.py transport layer: ConsoleSink (queue drained on the Tk thread via after(50ms)), stdout/stderr redirectors with ANSI stripping and \r (replace-last) handling, an mp.Queue log bridge for worker processes, logging handler, and a 5000-line ring buffer.
  • New gui/console_widget.py ConsoleWidget with stderr/info coloring, autoscroll toggle, clear, and close.
  • running-process==4.1.0 added to the runtime lock.

Tests

  • tests/test_core_proc.py — streaming, exit codes, cancel, default stdio output; parametrized over both backends (running-process tests skip when the package is absent, matching CI).
  • tests/test_gui_console_log.py — 20 tests covering line splitting, ANSI stripping, sink, redirectors, log handler, mp bridge, ring buffer.
  • Full suite: 281 passed, 2 deselected (Windows, py3.13). Ruff/flake8 clean.

🤖 Generated with Claude Code

zackees and others added 27 commits June 2, 2026 18:29
Prepare 2026.3 upstream main update
…es (#32)

ensure_AI_model_file now fetches {name}_fp16.onnx.zst from the asset
host and decompresses it atomically on first use. Adds zstandard to
the runtime lock.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…6 verification (#34)

ensure_AI_model_file now reads the per-model manifest from the assets
repo (assets/qualityscaler/onnx/<model>/manifest.json), downloads the
latest zstd archive from its LFS href, verifies the sha256, and
decompresses atomically.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… runtime

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ine pipeline

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat: layer QualityScaler into core API + CLI + GUI
…l wheels

onnxruntime-directml 1.24.4 only ships cp311-cp314 wheels, so the iso_env
resolve fails under the previous ==3.10.* pin. Python 3.11 is the only
version with wheels for every runtime pin (pillow 9.5.0 tops out at cp311).
Rename the runtime venv dir to runtime-py311 so stale 3.10 envs are not reused.

Closes #37

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(cli): bump managed runtime to Python 3.11
CI never exercised the managed runtime install, letting #37 ship broken.
Add a dry-run uv resolve of the runtime lock as a pytest integration marker,
an Install workflow that also builds the real runtime and runs the CLI
end-to-end on windows-latest, and the matching README badge.

Detect a source checkout from the working directory so `pip install . &&
qualityscaler --help` uses local code instead of a stale PyPI wheel at the
same version.

Closes #39
Closes #40

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(ci): install integration test, Install workflow, README badge
Replace the hardcoded -b:v 50000k with per-encoder rate-control args
anchored at HIGH = libx264 crf 18: CRF for x264/x265, -cq with -b:v 0
for NVENC, constant QP for AMF, and ICQ -global_quality for QSV.
Exposed as --video-quality on the CLI and a Video quality menu in the
GUI (persisted in user preferences, default HIGH).

Closes #42

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
… modules (#46)

Phase 1 of the GUI modularization plan: create src/qualityscaler/gui/
with constants, info_texts, media_info, state (UIState + label
conversions), and preferences (JSON load/save with identical keys and
fallbacks). QualityScaler.py now imports these instead of defining them
inline. Adds headless unit tests for state conversions and preference
round-trips.

Part of #44

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…hases 2-4) (#48)

QualityScaler.py shrinks from 1820 lines to a 12-line launcher shim; the GUI
now lives in qualityscaler/gui/ with the toolkit layer (customtkinter)
confined to widgets.py and app.py. The pipeline subprocess entry point moves
to gui/worker.py so spawned children no longer import the whole GUI, and the
toolkit-free controller (validation, settings building, process control) is
now unit tested headlessly.

Part of #44

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…classes (#50)

Replace the nested str->dict shapes in core/models.py and core/encoding.py
with frozen dataclasses (ModelManifestEntry, QualityTierArgs) so manifest
and ffmpeg quality data get typed attribute access instead of stringly
keyed lookups. Manifest parsing now raises a clear ValueError when a
required key is missing.

Closes #49

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Port the FluidFrames.RIFE frame-generation pipeline into a headless
qualityscaler.fluidframes package (RIFE/RIFE_Lite ONNX interpolation,
x2/x4/x8 and slowmotion options) and partition the GUI into
Quality Scaler and Fluid Frames tabs via CTkTabview. Each mode keeps
its own state, controller, worker process and preference file.
encode_video gains a keyword-only include_audio flag so slowmotion
output can drop the audio track.

Closes #51

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…e cache (#54)

Phase 0: declare [tool.uv] cache-keys over src/** so the managed runtime
rebuilds the wheel when source files change instead of silently reusing a
stale cached build (root cause of the invisible Fluid Frames tab).

Phase 1: add FrameGenJob, a background frame-generation job mirroring
UpscaleJob (start/cancel/events/wait/done), exported from
qualityscaler.fluidframes.

Phase 2: add a "fluidframes" CLI subcommand (model, factor, slowmotion,
resize, codec, quality, cpus, keep-frames) sharing the event-streaming
loop with "upscale", plus "models --fluidframes".

Also sync uv.lock to project version 2026.3.1.

Closes #53

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
#56)

Outside a source checkout, _self_requirement() pinned
quality-scaler==<installed_version>, which the runtime venv resolved
from PyPI. After a manual `pip install .` the index wheel at the same
version can be an older artifact, so the runtime ran stale code and
failed with "No module named qualityscaler.cli_runtime".

Build a wheel from the files of the installed distribution instead and
install the runtime from that file:// wheel; a content-derived build
tag in the filename forces a runtime re-sync whenever the installed
code changes. Fall back to the version pin only when re-packing is
impossible (editable installs are covered by checkout detection).

Bump version to 2026.3.2 so the next publish supersedes the stale
PyPI 2026.3.1 artifact.

Closes #55

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Add gui.file_chooser module (toolkit-free) that captures LAUNCH_CWD at
import time and maintains a shared in-session last-used directory.  All
four filedialog call sites in app.py and ff_panel.py now pass
initialdir=get_initial_dir() and call update_last_used_dir() on every
successful pick so consecutive dialogs open at the previously visited
location.  Gracefully falls back when any recorded directory no longer
exists on disk.

Closes #60
…aming

Adds a collapsible console panel below the tabview that mirrors
stdout/stderr from the GUI process and from worker processes, and
routes ffmpeg through a streaming subprocess runner (running-process
when available, Popen reader-thread fallback otherwise).

- core/proc.py: stream_subprocess with cancel/tick, live line callbacks
- gui/console_log.py: ConsoleSink, redirectors, mp.Queue log bridge,
  ANSI/CR handling, ring buffer
- gui/console_widget.py: ConsoleWidget with stderr coloring, autoscroll
- controllers/workers pass a log queue so child output reaches the GUI
- running-process==4.1.0 added to the runtime lock

Closes #62

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@zackees

zackees commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Opened against the wrong base repo by mistake — this work targets the zackees fork. Apologies for the noise.

@zackees zackees closed this Jun 12, 2026
@zackees
zackees deleted the feat/gui-console branch June 12, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant