Skip to content

Split the video block into parts, and drive the real app in tests - #330

Merged
vzhd1701 merged 14 commits into
masterfrom
video-block-refactor
Oct 14, 2026
Merged

vzhd1701 merged 14 commits into
masterfrom
video-block-refactor

Conversation

@vzhd1701

Copy link
Copy Markdown
Owner

Summary

gridplayer/widgets/video_block.py goes from 6716 to 2211 lines. The block keeps its core (loading, playback, seeking, rate, Qt events); everything else moves into parts, one VideoBlockPart per file, kept on the block as attributes:

Part File
status video_block_status.py
bookmarker video_block_bookmarker.py
view video_block_view.py
chapters, sponsorblock video_block_chapters.py, video_block_sponsorblock.py
subtitles, audio video_block_subtitles.py, video_block_audio.py
loop, screenshot video_block_loop.py, video_block_screenshot.py
recovery video_block_recovery.py
stream video_block_stream.py
navigation video_block_navigation.py
  • Commands name a part's method with a dot, ("active", "navigation.next"); cmd_active, manual_seek/manual_view/manual_rate and LOADING_COMMANDS resolve them. The only_* guards work on part methods.
  • Callers (actions, menus, managers, dialogs) and tests are pointed at the parts. Behaviour is meant to be unchanged; each commit is one part and passes the suite on its own.
  • tests/test_dotted_commands.py checks that every command named by the menus, keys, dialogs and LOADING_COMMANDS, and every all_* signal's slot, resolves to a method.

Real-app tests

tests/real_app drives the real player offscreen with VLC, through its menus, from scenarios:

  • media.py makes the media with ffmpeg into build/test-media/ on first use (nothing binary in git).
  • driver.py / run.py: uv run python -m tests.real_app.run <scenario or file.py> runs one and leaves a report, the app log and screenshots.
  • Scenarios: playback, folder and list navigation, stream sizes and a failing link, dubs, audio and subtitle tracks, chapters/bookmarks/SponsorBlock (answered locally).
  • external_audio_jump is a known bug, marked xfail(strict): with an external audio file attached, choosing a subtitle track makes VLC move the video back to where the file was added. The app only sets the track; the same happens before this refactor.
  • uv run pytest leaves them out (addopts = -m 'not real_app'); just test-real-app runs them, and so does a new real-app CI job, which uploads the runs' reports on failure.

AGENTS.md documents the parts pattern and how to drive the real app.

Testing

  • uv run pytest: 6374 passed, 9 skipped.
  • uv run pytest -m real_app: 6 passed, 1 xfailed, about 50s; repeated 9 times, three at a time, without a failure.
  • Before the real-app tests existed, the same scenarios were run against this branch and against the commit it started from, comparing the state at every step: no differences.
  • ruff, ruff format and rumdl clean; translate call count unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX


Generated by Claude Code

claude added 14 commits October 14, 2026 13:01
The guards on its commands, the floating stacked layout, the delay texts,
the reopen checks, SyncedView and the SponsorBlock seek mark each go where
the rest of their kind already is, leaving video_block.py to the block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
cmd_active, is_active_runtime_param_set_to and the block's manual_seek,
manual_view and manual_rate take a dotted name, "bookmarks.next", as well
as a plain one, so a part of the block taken out into an object of its own
keeps its commands without the block passing each of them on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
What is done with a video's bookmarks -- adding, jumping, the marker's
menu, renaming, colouring, the manager -- goes to block.bookmarker, its
commands reached as "bookmarker.add" and the like. The guards on the
block's commands ask the block a part belongs to (VideoBlockPart), and
the block keeps the bookmarks themselves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
Zooming, the zoom to a selection and its hint, following another video's
view, crop, alignment and moving the picture go to block.view, their
commands reached as "view.scale_increase" and the like. manual_view stays
with the block, the others following from there as they do for seeks and
speeds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
…arts

The chapters, and the jumps and loops between them, go to block.chapters,
the chapters themselves now block.chapters.all and their commands
"chapters.next" and the like. What SponsorBlock found, what of it is
marked and the skipping go to block.sponsorblock. The block keeps what the
site said of the video, its YouTube ID and length, for both to go by, and
puts the seek bar's marks together from them and the bookmarks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
The subtitle tracks, the choice among them, the files kept beside the
video, the preferred languages, the encoding and the delay go to
block.subtitles, their commands "subtitles.set_track" and the like. A
stand-in for the block in tests is given its parts by mock_block_parts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
The audio tracks and the choice among them, the output devices, the
preferred languages, the files kept beside the video, the channel mode and
the delay go to block.audio, their commands "audio.set_track" and the
like. A dub served as another stream is still the block's to follow, now
as follow_audio_language, the audio part going to it for that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
…e into its audio

Screenshots are a ScreenshotControl at block.screenshot ("screenshot.take"),
setting the loop a LoopControl at block.loop ("loop.set_start",
"loop.toggle_random", ...), and volume and mute go to block.audio, which
already had everything else about the sound. The loop's bounds, loop_start
and loop_end, stay on the block with the seeking that reads them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
…coveryControl

Reloading a network video that failed, the countdown to it, and going on
past an item of the list that would not play are block.recovery, with the
settings for both ("recovery.set_retry_mode", "recovery.set_fail_action",
...). The block no longer reaches into its timers and counters: it asks
for retries, note_played(), stop(), stop_skip() and the like. Showing the
error itself stays with the block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
…Control

Which rung of the quality ladder plays, fitting it to the pane, which dub
of a video served one language at a time, what the stream proxy is handed,
and reloading a live video every so often are block.stream
("stream.switch_quality", "stream.set_audio_language", ...). The proxy's
failure for the stream is asked of it too, as it knows the URL. The
ladders themselves, block.streams, stay where the resolver puts them.

Tests that borrowed the block's methods one by one now drive a real
StreamControl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
…tusControl

How far a video got loading, the error card and its Details window, and
which face the overlay shows with them are block.status, along with what
they keep: last_error, load_stage, load_steps, is_kept_for_picture.
Failing itself (error, network_error) stays with the block, which hands
the card the tries made.

The parts now read the block's is_error and is_closing rather than its
private fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
…o a NavigationControl

Going to the next, previous or a random video along the cell's list or
through its folder, taking up and leaving a list, its List window and the
place on it said over the video, and going on from a file that is gone are
block.navigation ("navigation.next", "navigation.leave_list", ...). Which
list and item the cell is on, media_list and list_item, stay with the
block. "playlist" already names the saved grid, hence not that.

Every part is now made before the URL resolver, which is wired to them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
…ment the block parts

A misspelt "part.method" is no error until its menu item is chosen, and
then the item just does nothing. The test reads the commands named for
the active block, the ones let through while loading, the ones sent to
all blocks and the signals that carry those to each block, and checks
each one is there.

AGENTS.md now says how the video block is split into parts and what to
watch for when moving code into one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
…debug run can use

tests/real_app starts the player offscreen with VLC, in a process of its
own, and runs a scenario against it: open media, choose menu items, wait
for the state to follow, check it, take screenshots. ffmpeg makes the
media into build/test-media on first use (clips, a list, a file with
chapters and audio and subtitle tracks and files beside it, a stream in
three sizes and a dubbed one), and HLS is served from the run itself.

The scenarios in tests/real_app/scenarios cover playback, folder and list
stepping, stream sizes and a failing link, dubs, tracks, and chapters,
bookmarks and SponsorBlock (answered locally). One shows a known bug, VLC
moving the video back on a track change while an audio file plays beside
it, and is expected to fail until it is fixed.

They run with `just test-real-app` (pytest -m real_app) and as a CI job
of their own; a plain pytest leaves them out. A one-off scenario file
runs with `python -m tests.real_app.run`, which AGENTS.md describes along
with what the driver takes care of.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTS355x5fJ6RENK1HLYqmX
@vzhd1701
vzhd1701 merged commit 3cf651c into master Oct 14, 2026
4 checks passed
@vzhd1701
vzhd1701 deleted the video-block-refactor branch October 14, 2026 18:16
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.

2 participants