Miscellaneous fixes (#23082)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions

* openvino log message and preview directory checks

* restrict config vars for viewer users

* recording timestamp fix

when startTime is exactly on an hour boundary, findIndex returns the first matching chunk, which is the previous hour's chunk (where before == startTime), instead of the correct chunk (where after == startTime)

the bug shows up when using the share timestamp feature and sharing a specific timestamp on the exact hour mark. when accessing the shared link, the timeline would jump to the incorrect hour

* use helper for chunked time range

* Adjustments to contributing docs

* tweak

* Improve wording

* tweak

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
Josh Hawkins 2026-05-01 12:25:26 -05:00 committed by GitHub
parent ba4a6a53d7
commit 45213d0420
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 51 additions and 13 deletions

View File

@ -10,11 +10,14 @@ If you've found a bug and want to fix it, go for it. Link to the relevant issue
### New features ### New features
Every new feature adds scope that the maintainers must test, maintain, and support long-term. Before writing code for a new feature: A pull request is more than just code — it's a request for the maintainers to review, integrate, and support the change long-term. We're selective about what we take on, and prioritize changes that align with the project's direction and can be responsibly maintained in the long term.
**Large or highly-requested features** raise the bar even higher. Popularity signals demand, but it doesn't pre-approve any particular implementation. The bigger the change, the higher the long-term cost, and the more important it is that we're aligned on scope and approach before any code is written. A large PR that lands without prior discussion is unlikely to be merged as-is, no matter how well it's implemented.
Before writing code for a new feature:
1. **Check for existing discussion.** Search [feature requests](https://github.com/blakeblackshear/frigate/issues) and [discussions](https://github.com/blakeblackshear/frigate/discussions) to see if it's been proposed or discussed. Feature requests tagged with "planned" are on our radar — we plan to get to them, but we don't maintain a public roadmap or timeline. Check in with us first if you have interest in contributing to one. 1. **Check for existing discussion.** Search [feature requests](https://github.com/blakeblackshear/frigate/issues) and [discussions](https://github.com/blakeblackshear/frigate/discussions) to see if it's been proposed or discussed. Feature requests tagged with "planned" are on our radar — we plan to get to them, but we don't maintain a public roadmap or timeline. Check in with us first if you have interest in contributing to one.
2. **Start a discussion or feature request first.** This helps ensure your idea aligns with Frigate's direction before you invest time building it. Community interest in a feature request helps us gauge demand, though a great idea is a great idea even without a crowd behind it. 2. **Start a discussion or feature request first.** This helps ensure your idea aligns with Frigate's direction before you invest time building it. Community interest in a feature request helps us gauge demand, though a great idea is a great idea even without a crowd behind it.
3. **Be open to "no".** We try to be thoughtful about what we take on, and sometimes that means saying no to good code if the feature isn't the right fit for the project. These calls are sometimes subjective, and we won't always get them right. We're happy to discuss and reconsider.
## AI usage policy ## AI usage policy
@ -39,6 +42,8 @@ We're not trying to gatekeep how you write code. Use whatever tools make you pro
Some honest context: when we review a PR, we're not just evaluating whether the code works today. We're evaluating whether we can maintain it, debug it, and extend it long-term — often without the original author's involvement. Code that the author doesn't deeply understand is code that nobody understands, and that's a liability. Some honest context: when we review a PR, we're not just evaluating whether the code works today. We're evaluating whether we can maintain it, debug it, and extend it long-term — often without the original author's involvement. Code that the author doesn't deeply understand is code that nobody understands, and that's a liability.
One more thing worth saying directly: most maintainers already have access to the same AI tools you do. A PR that's entirely AI-generated — where the author can't explain the design, debug issues independently, or engage substantively in design discussions — doesn't offer something we couldn't produce ourselves. What makes a contribution genuinely valuable is the human judgment and domain understanding behind it, as well as the engagement during review that shapes it into something we can confidently take on long-term.
## Pull request guidelines ## Pull request guidelines
### Before submitting ### Before submitting

View File

@ -146,8 +146,13 @@ def config(request: Request):
for name, detector in config_obj.detectors.items() for name, detector in config_obj.detectors.items()
} }
# remove the mqtt password # remove environment_vars for non-admin users
if request.headers.get("remote-role") != "admin":
config.pop("environment_vars", None)
# remove mqtt credentials
config["mqtt"].pop("password", None) config["mqtt"].pop("password", None)
config["mqtt"].pop("user", None)
# remove the proxy secret # remove the proxy secret
config["proxy"].pop("auth_secret", None) config["proxy"].pop("auth_secret", None)

View File

@ -52,6 +52,12 @@ class OvDetector(DetectionApi):
self.h = detector_config.model.height self.h = detector_config.model.height
self.w = detector_config.model.width self.w = detector_config.model.width
logger.info(
"Loading OpenVINO model %s on device %s",
detector_config.model.path,
detector_config.device,
)
self.runner = OpenVINOModelRunner( self.runner = OpenVINOModelRunner(
model_path=detector_config.model.path, model_path=detector_config.model.path,
device=detector_config.device, device=detector_config.device,

View File

@ -349,6 +349,13 @@ def move_preview_frames(loc: str) -> None:
if not os.path.exists(preview_holdover): if not os.path.exists(preview_holdover):
return return
if not os.access(preview_holdover, os.R_OK | os.W_OK):
logger.error(
"Insufficient permissions on preview restart cache at %s",
preview_holdover,
)
return
shutil.move(preview_holdover, preview_cache) shutil.move(preview_holdover, preview_cache)
except shutil.Error: except shutil.Error:
logger.error("Failed to restore preview cache.") logger.error("Failed to restore preview cache.")

View File

@ -361,14 +361,17 @@ class PreviewRecorder:
small_frame, small_frame,
cv2.COLOR_YUV2BGR_I420, cv2.COLOR_YUV2BGR_I420,
) )
cv2.imwrite( cache_path = get_cache_image_name(self.camera_name, frame_time)
get_cache_image_name(self.camera_name, frame_time),
if not cv2.imwrite(
cache_path,
small_frame, small_frame,
[ [
int(cv2.IMWRITE_WEBP_QUALITY), int(cv2.IMWRITE_WEBP_QUALITY),
PREVIEW_QUALITY_WEBP[self.config.record.preview.quality], PREVIEW_QUALITY_WEBP[self.config.record.preview.quality],
], ],
) ):
logger.error("Failed to write preview frame to %s", cache_path)
def write_data( def write_data(
self, self,

View File

@ -38,6 +38,22 @@ export function getChunkedTimeDay(timeRange: TimeRange): TimeRange[] {
return data; return data;
} }
/**
* Find the chunk index that contains the given timestamp.
* Uses half-open intervals [after, before) for all chunks except the last,
* which uses a closed interval [after, before] so the terminal boundary
* is always reachable.
*/
export function findChunkIndex(chunks: TimeRange[], timestamp: number): number {
return chunks.findIndex((chunk, i) => {
const isLast = i === chunks.length - 1;
return (
chunk.after <= timestamp &&
(isLast ? chunk.before >= timestamp : chunk.before > timestamp)
);
});
}
export function getChunkedTimeRange( export function getChunkedTimeRange(
startTimestamp: number, startTimestamp: number,
endTimestamp: number, endTimestamp: number,

View File

@ -26,7 +26,7 @@ import {
ReviewSummary, ReviewSummary,
ZoomLevel, ZoomLevel,
} from "@/types/review"; } from "@/types/review";
import { getChunkedTimeDay } from "@/utils/timelineUtil"; import { findChunkIndex, getChunkedTimeDay } from "@/utils/timelineUtil";
import { import {
MutableRefObject, MutableRefObject,
useCallback, useCallback,
@ -169,9 +169,7 @@ export function RecordingView({
[timeRange], [timeRange],
); );
const [selectedRangeIdx, setSelectedRangeIdx] = useState( const [selectedRangeIdx, setSelectedRangeIdx] = useState(
chunkedTimeRange.findIndex((chunk) => { findChunkIndex(chunkedTimeRange, startTime),
return chunk.after <= startTime && chunk.before >= startTime;
}),
); );
const currentTimeRange = useMemo<TimeRange>( const currentTimeRange = useMemo<TimeRange>(
() => () =>
@ -274,9 +272,7 @@ export function RecordingView({
const updateSelectedSegment = useCallback( const updateSelectedSegment = useCallback(
(currentTime: number, updateStartTime: boolean) => { (currentTime: number, updateStartTime: boolean) => {
const index = chunkedTimeRange.findIndex( const index = findChunkIndex(chunkedTimeRange, currentTime);
(seg) => seg.after <= currentTime && seg.before >= currentTime,
);
if (index != -1) { if (index != -1) {
if (updateStartTime) { if (updateStartTime) {