Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reset image and clips recording options #15

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open

Add reset image and clips recording options #15

wants to merge 20 commits into from

Conversation

pilgrimtabby
Copy link
Owner

@pilgrimtabby pilgrimtabby commented Dec 26, 2024

Reset Images and Recordings

This PR adds two major new features:

  1. Reset image: Adding the flag {r} to an image name turns the image into a reset image. If the reset image is detected, the reset hotkey is pressed and the splits are reset. When a reset image exists, you can view it by pressing and holding on the split image box in the UI. The reset image isn't looked for on the first split image -- this first image is treated as a "start image," so the run isn't considered "started" until after the first image is found.

  2. Recording clips: If enabled, a recording of each split will be saved, beginning at the end of the last split and ending when the split hotkey is pressed or the split image is detected by the splitter. Moving to other splits, resetting, changing FPS or destination folder settings, exiting the program, and resetting will result in the current recording being discarded instead of saved. To enable this setting, click on the video feed (you will see a red or gray circle appear in the upper-right corner). To change the destination folder for saved clips, double-click the video feed. Like the reset image, this setting isn't enabled on the first split image.

For a detailed changelog, see the commit messages from Oct. 15, 2024 to the present.

- Bump version to 1.1.0
- Modify various methods in split_dir to search for, account for, and separately track a reset image (optional)
- Add a reset flag option to split_dir._SplitImage ({r}) that creates a reset image. There can only be one reset image, and it can be triggered at any time, during any split, except during suspend and delay periods and during the very first split image.
- Modify splitter.py to look for matches to the reset image, if it exists, while checking for matches to the split images. To accomplish this, split out a portion of _look_for_match into a few different methods, and modify get_match_percent to require supplying a template as well as a potential match
- Add _reset() method to splitter.py to handle the reset action taken by the reset image when a match is found
- Add ImageType enum to track which kind of split splitter._split() should execute
- Add ability to click and hold on the split image in the UI to see the split image and its corresponding match percents (see ui_controller.py and edits to ui_main_window.py)
-Hovering over the video feed or (if a reset image exists) the split image shows an intuitive interface that makes it clearer you can click it
-Add flag to track whether clips should be recorded (no functionality yet); this is toggled by clicking on the video feed
-Added some comments to splitter.py
-Removed set_style from ui_style_sheet as it is no longer used
-No longer show/hide reset image using pyqtSignals. Just read the flags from the ClickableQLabel every frame and act accordingly
-Showing the reset image also shows the image's name and a "Reset image" label
-Reset image now displays correctly even if it is the only split image in a directory
splitter.py:
-Rename _compare_thread to compare_thread since it makes more sense to just check if it's alive directly in ui_controller. Also correct some documentation typos

ui_controller.py:
-Correct some typos in documentation in ui_controller.py
-Add flag to track whether recording should be allowed (self._record_clips_enabled). This flag is True when we're not on the first split and when compare_thread is alive. This is tracked separately from whether the user actually has recording turned on. Both conditions must be true for recording to occur.
-Place the methods that were formerly part of submethods under _poll into _poll directly -- their classification was no longer needed or helpful.
-Add methods and CSS to handle the location and size of video_overlay

ui_main_window:
-Add video_overlay to ui_main_window to hold the record symbol
pilgrim_autosplitter.py:
-Wait 0.2 seconds before quitting (this prevents segfaults from singleshot QTimers not having finished yet in ui_main_window)

ui_controller.py:
-Add _get_style_sheet to simplify setting styles
-Move update checking / message box logic to this module
-Connect double click logic (doesn't do anything yet)
-Set message box styles here instead of assigning them a parent widget in ui_main_window (prevents weird bug where the boxes wouldn't close when pressing "OK")
-Add action for "later" button on update message box so that button actually does something
-Add logic to prompt a double-click to change recording location (not implemented yet)

ui_main_window.py:
-Rename video_overlay to video_record_overlay, and add video_info_overlay
-Add eventFilter to prevent mouse clicks from doing anything when clicking into focus on certain widgets
-Apply eventFilter to split_display and video_display
-Add double click functionality to ClickableQLabel
settings.py:
-Add LAST_RECORD_DIR setting that points to folder where recordings are saved
-Add validation so LAST_RECORD_DIR always points to a real directory somewhere within the user's home dir

ui_controller.py:
-Add logic for _set_record_dir_path. This method is called when double-clicking the video feed (when the video feed is on)
-Show recordings destination folder hint when turning on recording
-Correctly position video_info_overlay in each aspect ratio

ui_main_window.py:
-Add class PaintedQLabel, which creates black-outlined white text that fades out 3s after being set
splitter.py:
-Rename start() to restart() -- all the start_x_thread methods now include a call to safe_exit_x_thread at the beginning, so we can call this method whenever know we want to kill + start a thread at the same time
-Structure of the threads has changed significantly. Now, there are four threads -- capture, compare_split, compare_reset, and record (not yet implemented). Splitting the two "compare"s into their own threads allows us to look for the reset image even when the compare_split thread is suspending or delaying between splits, which was a major flaw behind doing both in one thread. The capture thread now feeds its frames into dedicated queues for each thread, so there's no need to track FPS anywhere except in capture_thread.
-Accordingly, the FPS factor logic, etc., has been moved to _capture, and those methods have been moved to the capture_thread section.
-All references to the compare_thread have been modified to include both compare threads (and the record thread if necessary)

-ui_controller.py:
-main_window's style sheet (for showing mouse interactions w/ video and splits) is only updated when the style sheet actually changes. This cuts CPU usage by about 40% (!!) on my machine
-The record icon is only resized when the aspect ratio changes. This saves about 6% CPU on my machine.
-References to splitter methods have been modified in accordance with the notes in splitter.py above
-Slightly adjusted placement of video_info_overlay because of the changes made in ui_main_window.py (see below)

ui_main_window.py:
-Moved some info about the video_info_overlay to this module
-Replaced PaintedQLabel with ShadowFadeQLabel. Instead of outlining the text, a pseudo-drop shadow effect is applied. Since there's also a fade effect and you can't apply more than one graphical effect without a lot of pain, I added this class. It uses two QLabels inside a container widget and places them so it looks like the text has a drop shadow. PaintedQLabel was working pretty well, but when it showed text on screen it was causing a massive CPU spike (CPU load roughly doubled for around 10 seconds). This new class causes a CPU increase when text updates of only 2-3% on my machine.
-Updated some typehints
splitter.py:
-Rename all "start_x_thread" methods to "restart_x_thread" since they also kill past instances now
-Don't kill record_thread in toggle_suspended anymore (no point in doing so, it messes up recordings)
-Implement _record. Recordings for dummy splits are concatenated onto the next split. Recordings that end with a split hotkey press or a normal / pause split detected are saved, all others are deleted.

ui_controller.py:
-All _request_x_split methods kill record_thread at the beginning, then restart it at the end, to ensure the recordings only capture a single split and are handled properly
-New splitter flag recording_enabled added to replace record_clips_enabled

I'm satisfied with this implementation -- I can't believe it, but it barely seems to add any CPU / memory overhead (on my machine at least). Seems to work pretty well. No audio, but I don't care, and I'm not sure that's possible without using FFMPEG and I don't want to use their license. Definitely needs to be playtested but I'm happy with the progress.
-Add split name (minus flags and settings text) to recording name when saving recording
-Add loop # when saving a recording from a split image with multiple loops
-Fix bug where dummy split recordings were saved, not continued, when splitting using a hotkey press
-Fix bug where last split image's recording didn't save when split was triggered by _compare_split
-Fix bug where recording didn't restart when unpausing comparisons on last split image after splitting on last split image
-Remove redundant suspended and delaying flags in splitter.py -- can just look at whether suspend_remaining and delay_remaining are not None, or whether compare_split_thread is alive
-splitter._compare_reset no longer loops indefinitely -- it doesn't need to since the thread will always come back alive after dying if there's a reset image. Not looping prevents an awkward screen transition after resetting with a delay, too.
-Fixed a bug where a delaying reset image wouldn't take precedence over a delaying / suspended split image, messing with the display. Now, the reset image being found will simply kill compare_split_thread and take over.
-The text displayed while delaying before a reset now says "resetting", not "splitting"
Used by inserting a float or int between percent signs in the reset split name. If you do this, the splitter waits that many seconds before looking for reset images after the first split. Useful if your first split is the same as the reset image.
Trying to read values from the capture card is a fool's errand, especially with cheap knockoff cards. This way, what the user sees in the video feed is what they'll get in the recorded clip.
Remove _capture_max_fps and _target_fps from splitter.py entirely -- there is no reliable way to get the capture card's max FPS when it's a cheapo card, and some platforms/ cards aren't supported by opencv at all as far as I can tell. The tradeoff is that we now calculate _fps_adjust_factor every frame even when the capture card is maxed out, but people who are maxing their cards out probably aren't too worried about performance anyway, and the performance tradeoff is extremely minor (it might even be offset by the fact that we are calling time.sleep more often).

The result of this should be that the user's FPS setting is always honored unless they choose a setting higher than their capture source can handle, in which case the capture's maximum FPS is forced (because cv2.get() blocks). This is probably what people expect anyway. It also means that clip recordings will ALWAYS be at the FPS the user has set, so the clip speed won't be distorted unless they set an FPS higher than their card can actually output. In that case, they should probably be using a lower setting anyway, since they will benefit from the performance boost. I will add a note about this to the user manual.
This prevents a newly introduced issue (when checking for capture's max FPS was removed) where setting a higher FPS than the capture source can handle would seriously distort the adjust factor. Resetting it after every FPS change removes this distortion each time it would make a difference in FPS.
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