Conversation
Adds a Camera tab and /api/camera/* endpoints to read the dashcam's current settings and safely change them over the Novatek netapp HTTP interface (http://<cam>/?custom=1&cmd=<id>&par=<value>). - viofosync_lib/_control.py: settings read (cmd=3014) decoded against a derived per-model command map; validated writes with read-back verify; a hard denylist of destructive commands (format / factory reset / firmware update / delete / reboot / restart-wifi / SSD format+delete) refused before any request is built; auto stop/resume recording for record-gated settings; gentle single-request transport so the camera's single-threaded daemon is never overrun. - web/routers/control.py + web/app.py: session/CSRF-protected REST API. - web/static: Camera tab UI — toggles for on/off settings, drop-downs for the rest, a recording pill, and read-only rows (with reasons) for settings the camera can't change now (resolution over Wi-Fi, exposure, lenses not attached). Lens-dependent settings re-enable automatically when the lens is connected (detected via the live sensor count). - viofosync_lib/data/command_map.json + scripts/build_command_map.py: a derived command/option map. Only the factual API data (command ids, keys, option enumerations) is reformatted from the VIOFO app's device-cmd-manager .db asset; the .db itself is not redistributed. - tests/test_control.py: offline unit tests (denylist, validation, support classification, read-back verify, record-gated retry). Validated end-to-end against an A329S; other models are mapped from the app data but untested. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Shows the Camera control UI on an A329S — toggles, the recording pill, and the read-only rows (resolution/exposure) the camera won't change over Wi-Fi. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…lity The log view rendered time-only timestamps, placed the exception expand caret at the far left of the row, and truncated long lines with no way to read them on a narrow screen. - Timestamps now show date + time (toLocaleDateString + toLocaleTimeString). - The expand caret moves into its own column immediately right of the date/time; the column is always reserved so levels stay aligned whether or not a row carries an exception. - The log list scrolls horizontally (overflow:auto) and rows grow to content width, so long lines are reachable everywhere; on screens <=720px each entry wraps instead, with the message full-width beneath the meta line. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The sync pause state lived only in an in-memory threading.Event, so pausing sync and restarting the app (or container) silently resumed it. Persist it in the kv table and restore on worker construction, so the worker comes back in the state the user last left it. ENABLE_SCHEDULED_SYNC still governs whether the schedule runs at all. - Add kv_get/kv_set helpers on Database (the kv table existed but was unused). - SyncWorker restores the flag in __init__ (best-effort: no store = nothing to restore) and pause()/resume() persist it. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Satisfies the repo's `ruff check` CI step (I001). Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add the derive_queue table (one row per clip) and its pure-DB state machine (enqueue/claim/done/transient-failure/reconcile/enqueue-missing) — the durable, restart-safe backbone for off-loop per-clip derivation. Co-Authored-By: Claude Opus 4.8 <[email protected]>
derive_one() runs the idempotent per-clip chain (duration via ffprobe, GPS, thumbnail, filmstrip), gated by the eager-derive settings and classifying failures into requeue/gone/done. The DeriveWorker async task drains derive_queue one clip at a time, sharing the generators' per-loop semaphores so background backfill yields to on-demand requests. Adds a filmstrip failed-recently skip marker; removes the superseded duration sweep. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Index + enqueue each clip as it downloads or imports (so it appears in the archive instantly) and on every scan via enqueue_missing. Start the DeriveWorker in the app lifespan with a one-time boot backfill of the existing archive. Remove the old boot/post-cycle/rescan thumb+duration sweeps (the worker subsumes them) and drop a clip's derive_queue row when retention prunes it. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add DERIVE_THUMBS_EAGER (default on) and DERIVE_FILMSTRIPS_EAGER (default off — filmstrips are ~10-25x the ffmpeg work, so opt-in), surfaced in a new Settings > Thumbnails section and projected in the settings GET response (with a regression test pinning that every editable key is projected). Route the worker's clip_derived event through the debounced archive refresh. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…weaks Post-merge tweaks to the camera-control feature and shared UI: - Camera tab: cache the last-known info/settings and render them instantly with controls disabled, refreshing in the background; controls re-enable only once the camera answers (a brief outage keeps the last-known view instead of blanking). Refresh button refreshes in place. - Camera settings rows are now label | status | control: status/marker right after the label, controls (toggle/select/value) always flush-right, uniform row heights via label padding. Not-adjustable settings show a compact ⓘ marker with the reason in a tooltip. - App-wide `.switch` toggle for on/off checkboxes (Settings + camera rows; selection checkboxes stay native), with the settings-pane sizing fixed so toggles aren't squashed. - Reusable `data-tip` tooltip: body-level, fixed-positioned, flips/clamps near edges, shown on hover, focus, and tap/click — replacing native `title`, which never fired on click/touch. - Reworded the Thumbnails settings hint. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Timeline export segments gain an optional `pip` channel. The piece builder carries each piece's absolute start + pip channel; a new overlay-filter builder and partner-clip resolver let the runner composite the PiP camera as a corner inset (per the global PIP_POSITION setting), falling back to a plain scale when no partner footage covers the window. Audio is unchanged. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add a PiP button (and "P" shortcut) that cycles a per-segment inset through the enabled cameras, skipping the segment's own. A green placeholder rectangle tracks the letterboxed preview video in the corner set by the global PIP_POSITION setting; the choice is scoped per segment, persists to localStorage, and is sent per segment to the export API. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add a `skipped` queue state (free-text column, no migration). `skip` moves pending/failed rows to skipped; `unskip` returns them to pending with a fresh attempt budget. next_pending only ever picks pending, so skipped files are never downloaded; reconcile leaves skipped rows alone. The day summary reports a skipped_count, and CSRF-guarded /api/queue/skip and /api/queue/unskip routes (mirroring /queue/retry) drive it. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Queue selection is broadened from pending-only to pending/failed/skipped, and skipped rows show a distinct badge + counts. The per-action buttons are replaced by an Actions dropdown + Apply (Download next / Skip / Clear skip / Retry failed) that posts the selection to the matching endpoint; the backend filters by state. "Download recent hours next" is kept; the retry-all button is removed in favour of selection-based retry. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…tive Show the clip filmstrip animated on hover in the archive view, matching the export-job thumbnail, and extract the scrub into a reusable, frame-count- agnostic primitive shared by both call sites. - Shared .film-scrub CSS class + film-scrub-anim keyframe: background-size is driven by a --frames custom property; the steps(N) timing is set inline from JS, since CSS steps() can't read a variable. - Shared JS helpers applyFilmstripScrub() / wireLazyFilmstripScrub() in app.js. - Export thumb migrated onto the primitive (fixed 10 tiles; behaviour-preserving — frames=10 reproduces the previous 1000% / steps(10) values exactly). - Archive tiles gain a pointer-events:none overlay over the static thumbnail, lazily loading each clip's variable-length filmstrip on first hover so opening a day doesn't spawn ffmpeg for every tile. Reduced-motion keeps the static thumbnail; single-frame and un-renderable (204) clips stay static; the existing click-to-open handler is unaffected. Co-Authored-By: Claude Opus 4.8 <[email protected]>
CI runs `ruff check .` and tests/** is linted; this file tripped I001 (an extra blank line after the import block). No behaviour change. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…on-tap - Camera settings rows now use a wrapping flex layout on small screens: the select drops to its own full-width line with even spacing while toggles and current values stay on the label's line, and empty status cells collapse. - Stop mobile zoom: 16px touch form controls (kills the iOS focus auto-zoom) plus maximum-scale=1,user-scalable=no in the viewport meta. Co-Authored-By: Claude Opus 4.8 <[email protected]>
GPS stop boundaries land ~50m (STOP_RADIUS_M) inside the real drive, so the pull-away clip at the start and the pull-in clip at the end fell outside the raw journey window — the auto-journey was "missing the start" and "cut short on arrival". Pad each edge of the /timeline journey window outward, bounded by the nearest parking-mode clip on that side (the genuine "car is parked" boundary) and capped at MAX_JOURNEY_BUFFER_S (120s). The cap is the backstop because the dashcam can be knocked out of parking mode by the car's electrics waking, so an arbitrary parking->driving switch isn't trusted — only a parking clip is a hard stop. The padded window is clamped to actual clip coverage so it never leads into empty timeline. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Added
Camera Control
A new Camera tab reads the dashcam's current settings and lets you adjust the safely-changeable ones over Wi-Fi — on/off toggles, drop-downs, and a recording indicator — each validated against the camera's own option list and read back to confirm it stuck. Destructive commands (format SD, factory reset, firmware update, delete, reboot) are hard-blocked and never shown, and the few record-only settings auto-pause then resume recording. Settings for 29 Viofo models are mapped from the official app's command database; the A329S is validated on hardware. Contributed by @droomurray (#21).
Three-Camera Support (Telephoto / Interior)
Telephoto (
T) and interior/cabin (I) clips are now first-class alongside front and rear. They sync, index, and pair into the same capture group, so a three-camera day shows a third thumbnail in the archive and a third track on the timeline. New exports cover them: Join Tele / Join Interior, plus picture-in-picture with the third camera fullscreen and the front camera as the inset (the front clip stays the audio source, so the microphone track is preserved). The clip viewer's camera key cycles through every camera present at a timestamp. Two-camera setups are visually unchanged. Contributed by @jusii (#18).Background Thumbnails & Filmstrips
Thumbnails and timeline filmstrips are now produced by a background worker as clips download — and existing clips are backfilled — so the archive and timeline populate as soon as footage arrives instead of after a sync cycle finishes. A new Thumbnails settings section controls it: thumbnail pre-generation is on by default, while the heavier filmstrip pre-generation is opt-in and otherwise falls back to generating on demand the first time a clip is viewed.
Per-Segment Picture-in-Picture in the Editor
The timeline editor's switched-camera cut can now carry a picture-in-picture inset on a per-segment basis. With a segment selected, press the PiP button (or P) to cycle the inset through your other cameras — it skips the segment's own camera — and a green placeholder shows where it will sit. The choice is remembered per segment and composited into the export, in the corner set by the global picture-in-picture position setting. A segment whose chosen camera has no overlapping footage simply exports without the inset.
Skip Downloads
You can now skip clips you don't want to sync. Select them in the download queue and choose Skip from the Actions menu; skipped clips get their own badge and are never downloaded. Clear skip returns them to the queue with a fresh set of retry attempts. Queue selection now spans pending, failed, and skipped clips, so one Actions menu — Download next / Skip / Clear skip / Retry failed — drives the whole list.
Changed
Fixed
/ROfolder; the download queue now refreshes the clip's source path when the camera re-reports it there, instead of exhausting its retry budget against the stale path and never syncing the clip. The dashcam-delete lock guard benefits too, since it keys off the same refreshed/ROpath. Contributed by @jusii (Refresh stale source_dir on already-queued rows #17).