Skip to content

Complete 3-channel support: telephoto (T) and interior (I) cameras#18

Merged
RobXYZ merged 4 commits into
RobXYZ:mainfrom
jusii:pr/third-camera
Jun 12, 2026
Merged

Complete 3-channel support: telephoto (T) and interior (I) cameras#18
RobXYZ merged 4 commits into
RobXYZ:mainfrom
jusii:pr/third-camera

Conversation

@jusii

@jusii jusii commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Awesome work here! I'm glad I didn't move forward with my ideas before, as you've done exactly (or better) what I had planned!

I have 3 camera version, so here's my suggestion to add support for it. It takes into account if 3rd camera is cabin cam or telephoto. Tried to follow your way of doing it and to not clutter the UI with all variations which third camera could bring.


A few details to complete the picture:

  • The third camera is the filename's trailing letter: T (telephoto, e.g. A329) or I (interior/cabin, e.g. A139 / A229 3CH). Before this, those files downloaded fine but the scanner glob skipped them and the archive day-pairer filed any non-F camera under "rear" — a T/I clip silently overwrote the real rear slot.
  • This completes the interior pre-wiring you already had in naming.py ("I": "interior") and extends the same to telephoto. Timeline needed no changes — channels are data-driven, so the third track just appears (the segment-channel validator now accepts tele; interior was already there).
  • New exports: join_tele / join_interior and pip_tele / pip_interior (third camera fullscreen + front inset). Front stays ffmpeg input 0 in every PiP variant so the mic audio is kept; existing pip / pip_rear invocations are bit-identical.
  • UI: the third thumb and its action buttons only show up when the data actually has that camera — 2-camera setups see a pixel-identical UI. The clip modal's F key now cycles through whatever cameras exist at that timestamp.
  • Tests cover parsing (T/PT/ET, I/PI/EI), pair-slot dispatch, export-type validation, PiP filter graphs, importer classification and timeline channel ordering. Validated live on my A329 (241 telephoto clips indexed + exports verified) and with real interior footage from my old cabin-cam setup.

Deliberately matched your existing hardcoded-letter style rather than introducing a camera registry, to keep the diff small — happy to do that refactor as a follow-up if you'd prefer it.

jusii added 4 commits June 11, 2026 16:05
…backend

Viofo 3-channel models record a third file per capture alongside
front (F) and rear (R): telephoto (…T.MP4, e.g. A329) or interior
(…I.MP4, e.g. A139/A229 3CH). These downloaded fine but were
invisible downstream: the scanner glob skipped them, the filename
parsers returned None, and the archive pairer filed any non-F
camera under "rear".

- queue.py: camera/event regexes [FR] -> [FRTI]
- _archive.py: scanner glob _*[FR].MP4 -> _*[FRTI].MP4
- naming.py: channel map gains T->tele (I->interior was already
  pre-wired); CHANNEL_ORDER/LABELS gain tele; export labels for
  the four new job types
- archive.py day pairer: explicit slot dispatch (F/T/I, else
  rear) and tele/interior slots in the response
- exporter.py: enqueue allowlist + join/pip dispatch for
  join_tele, join_interior, pip_tele, pip_interior; _pair_clips
  takes a required-slots tuple; _pip takes the partner slot.
  Front stays ffmpeg input 0 in every PiP variant — it carries
  the mic audio and default stream selection picks it; the
  filter helper needs no logic change since any non-front main
  already yields partner-fullscreen + front-inset
- exports.py: route patterns accept the new types and the tele
  segment channel (interior was already accepted)

The timeline editor needs no changes: channels are data-driven
via channel_of(), and timeline.js renders whatever channel list
the server sends.
- Day cards render a tele/interior thumb only when the pair has
  one; the thumbs grid auto-fits 2 or 3 columns (80px floor) so
  2-camera days are pixel-identical
- Selection tracks tele/interior clip ids; Originals / Join /
  PiP button groups for the third camera stay hidden until the
  selection contains that camera, so 2-camera setups never see
  them
- Modal viewer: the binary F<->R toggle becomes a cycle over
  cameras present at the current timestamp (F->R->T/I), still on
  the F key; single-camera timestamps keep the toggle disabled
- Queue badge labels Tele / Interior; shared kind-T/kind-I badge
  color (magenta, clear of the ok/rear/accent/warn/err hues)
- Import dropzone hint mentions telephoto & interior
…meline

- New test_queue_filename_parsing.py: T/PT/ET and I/PI/EI camera
  + event-type derivation, case-insensitivity, unknown-letter
  rejection
- New test_exporter_pair_clips.py: slot dispatch for triplets,
  front+tele / front+interior pairs, required-slots filtering,
  parking prefixes
- New test_export_types.py: route pattern + enqueue allowlist
  accept the four new job types and the tele segment channel,
  and stay closed to unknown types
- test_channel_of.py: tele mapping + updated CHANNEL_ORDER
- test_pip_filter.py: tele/interior-main graphs equal rear-main
  (the input swap lives in _pip, not the filter helper)
- test_naming.py: labels + download names for the new types
- test_importer.py: classify_event_type for T/PT/I/PI and a
  telephoto fixture in the scan manifest
- test_timeline_endpoint.py: 3-camera day exposes tele/interior
  channels ordered after rear with correct labels
…tring

The action-bar groups appear when the *selection* contains a
third-camera clip, not when the day's data does; and the
timeline channel test deliberately mixes T and I in one day to
pin ordering, which the docstring now says outright.
@RobXYZ RobXYZ merged commit b648825 into RobXYZ:main Jun 12, 2026
1 check passed
@RobXYZ

RobXYZ commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Thanks for this, really appreciated!

I agree about not cluttering the UI, it needs a bit of thought about how to support additional cameras on the export bar. I'm not sure whether PiP is useful so perhaps those buttons can go.

A camera registry is a good idea as well if you’re happy to do that.

@jusii

jusii commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Alright, good I was at right track. I will take a look into camera registry.

jusii added a commit to jusii/viofosync that referenced this pull request Jun 12, 2026
@RobXYZ RobXYZ mentioned this pull request Jun 17, 2026
Merged
RobXYZ added a commit that referenced this pull request Jun 18, 2026
### 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](https://github.com/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](https://github.com/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

- On/off controls across the app are now toggle switches, with one tooltip style used app-wide that works on hover, keyboard focus, and tap.
- The sync **pause** state is remembered across restarts instead of resetting to running.
- The download queue's per-action buttons are now a single **Actions** menu with **Apply**; selection-based **Retry failed** replaces the old retry-all button, while **Download recent hours next** is unchanged.
- Archive thumbnails animate on hover, scrubbing the clip's filmstrip — the same preview the Export Jobs list already offered. They fall back to the static thumbnail when no filmstrip is available, and respect reduced-motion.

### Fixed

- Locking a clip on the dashcam between sync cycles moves it into the camera's `/RO` folder; 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 `/RO` path. Contributed by [@jusii](https://github.com/jusii) (#17).
- The Logs view now shows the date alongside the time and stays readable on a phone — long lines wrap rather than being clipped off-screen.
- Tapping a drop-down or text field no longer zooms the page in on mobile, and the interface no longer pinch-zooms — it behaves like a fixed app viewport.
- Auto-detected journeys on the timeline no longer miss the start of a drive or cut short on arrival — the journey window is padded past the GPS stop radius to take in the pull-away and pull-in clips, bounded by the surrounding parking footage.
@jusii jusii deleted the pr/third-camera branch June 18, 2026 12:41
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