Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
id: manifest
env:
UPDATE_BASE_URL: ${{ vars.DASHCAM_OFFLOADER_UPDATE_BASE_URL || 'https://dashcam-offloader-updates.vortexradar.workers.dev' }}
VERSION: 0.1.28
VERSION: 0.1.29
ZIP_NAME: ${{ steps.package.outputs.zip_name }}
ZIP_SHA256: ${{ steps.package.outputs.sha256 }}
SHORT_SHA: ${{ steps.package.outputs.short_sha }}
Expand Down
7 changes: 7 additions & 0 deletions Sources/DashcamOffloaderApp/CardScannerParkingInference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ extension CardScanner {
if blackVueParkingPaths.contains(clip.relativePath) {
continue
}
// On T330W the owner's RO/PF clips included time-lapse parking.
// RO + parking suffix alone cannot establish an impact event.
if profileID == "viofo-t330w" &&
relativeFolderPath(for: clip.relativePath).lowercased() == "dcim/movie/ro" &&
hasParkingChannelSuffix(clip.filename) {
continue
}
if let explicitPattern = explicitParkingPattern(for: clip) {
inferredByRelativePath[clip.relativePath] = explicitPattern
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/DashcamOffloaderApp/KnownDashcamCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ enum KnownDashcamCatalog {
model("Vueroid", "D10-F2W", channels: 2, roles: ["front", "rear"]),

// VIOFO
model("VIOFO", "T330W", channels: 3, roles: ["front", "rear", "interior"], notes: "T330 family with a different rear-camera variant. Exact T330W model stamp confirmed in owner-provided video frames; shared filenames alone are not an exact identifier."),
model("VIOFO", "T330", channels: 3, roles: ["front", "rear", "interior"], notes: "T330 family; the connected rear camera distinguishes T330W. Exact non-W OSD wording has not been sampled."),
model("VIOFO", "A329S", channels: 3, roles: ["front", "rear", "interior"], notes: "Multiplex video capable."),
model("VIOFO", "A329T", channels: 3, roles: ["front", "rear", "telephoto"], notes: "Telephoto channel token T observed."),
model("VIOFO", "A229 Pro", channels: 3, roles: ["front", "rear", "interior"]),
Expand Down
47 changes: 47 additions & 0 deletions Sources/DashcamOffloaderApp/VerificationTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,53 @@ enum VerificationTest {
try FileManager.default.createDirectory(at: emptySource, withIntermediateDirectories: true)

let scanner = CardScanner()
guard let t330WProfile = profiles.first(where: { $0.id == "viofo-t330w" }),
t330WProfile.osdSpec?.matchStrings == ["VIOFO T330W"],
KnownDashcamCatalog.models.contains(where: { $0.manufacturer == "VIOFO" && $0.model == "T330W" }) else {
print("VERIFY FAIL: T330W exact-stamp profile or catalog identity missing")
return false
}
let t330WSource = temp.appendingPathComponent("Unlabeled-T330W-fixture", isDirectory: true)
for folder in ["DCIM/Movie", "DCIM/Movie/Parking", "DCIM/Movie/RO", "DCIM/Photo"] {
try FileManager.default.createDirectory(at: t330WSource.appendingPathComponent(folder), withIntermediateDirectories: true)
}
let t330WNames = ["2026_1010_152138_000028F.MP4", "2026_1010_152138_000029I.MP4", "2026_1010_152138_000030R.MP4"]
for name in t330WNames {
try Data([1]).write(to: t330WSource.appendingPathComponent("DCIM/Movie/\(name)"))
}
for name in ["2026_1010_155138_000031PF.MP4", "2026_1010_155138_000032PI.MP4", "2026_1010_155138_000033PR.MP4"] {
try Data([1]).write(to: t330WSource.appendingPathComponent("DCIM/Movie/RO/\(name)"))
}
let t330WithoutStamp = try scanner.scan(sourceURL: t330WSource, profiles: profiles)
guard t330WithoutStamp.selectedProfile?.id == DashcamProfile.genericNewDashcam.id,
!t330WithoutStamp.clips.contains(where: { $0.channel == "unknown" }) else {
print("VERIFY FAIL: T330W shared VIOFO layout was falsely exact-identified or lost channels")
return false
}
if let ownerFrameVideo = ProcessInfo.processInfo.environment["DASHCAM_T330W_VIDEO_FIXTURE"] {
let fixtureURL = URL(fileURLWithPath: ownerFrameVideo)
let frontURL = t330WSource.appendingPathComponent("DCIM/Movie/\(t330WNames[0])")
try FileManager.default.removeItem(at: frontURL)
try FileManager.default.copyItem(at: fixtureURL, to: frontURL)
let t330WithStamp = try scanner.scanWithOSD(sourceURL: t330WSource, profiles: profiles)
guard t330WithStamp.selectedProfile?.id == t330WProfile.id,
t330WithStamp.clips.contains(where: { $0.channel == "front" }),
t330WithStamp.clips.filter({ $0.relativePath.contains("/RO/") }).allSatisfy({ $0.mode != "parking_impact_detection" }),
t330WithStamp.diagnostics.contains(where: { $0.stage == "osd_ocr_probe" && $0.profileID == t330WProfile.id && $0.outcome == "matched" }) else {
print("VERIFY FAIL: T330W stamp OCR, channels or neutral RO parking classification")
return false
}
if let noStampVideo = ProcessInfo.processInfo.environment["DASHCAM_T330W_NO_STAMP_VIDEO_FIXTURE"] {
try FileManager.default.removeItem(at: frontURL)
try FileManager.default.copyItem(at: URL(fileURLWithPath: noStampVideo), to: frontURL)
let noStampScan = try scanner.scanWithOSD(sourceURL: t330WSource, profiles: profiles)
guard noStampScan.selectedProfile?.id == DashcamProfile.genericNewDashcam.id,
!noStampScan.diagnostics.contains(where: { $0.stage == "osd_ocr_probe" && $0.profileID == t330WProfile.id && $0.outcome == "matched" }) else {
print("VERIFY FAIL: card without a model stamp falsely matched T330W")
return false
}
}
}
guard let arc700Profile = profiles.first(where: { $0.id == "thinkware-arc-700" }),
let arc900Profile = profiles.first(where: { $0.id == "thinkware-arc-900" }) else {
print("VERIFY FAIL: ARC 700/900 manual-backed seed profiles did not load")
Expand Down
17 changes: 17 additions & 0 deletions docs/card-profiles/viofo-t330w.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# VIOFO T330W (T330-family rear-camera variant)

Status: experimental, owner-submitted card samples and three owner-provided frame observations. Firmware reported by owner: `1.0_260910`.

## Exact model recognition

The owner-provided front, rear, and interior frames show the **`VIOFO T330W`** on-screen model stamp near the bottom center. The model stamp is user-configurable and may be disabled. Probe front `F` footage with bottom-strip OCR; only an exact model-stamp match should disambiguate this camera from VIOFO A229/A329 siblings. A generic VIOFO card layout or filename suffix is **not** proof of T330W.

T330 and T330W share the same main-camera family; the attached rear camera is a variant. The non-W model's exact OSD stamp has not yet been confirmed. Do not assume a W stamp identifies a different main platform or assume identical measured rear bitrate without testing.

## Card evidence

Two sanitized learning submissions from one owner confirmed `DCIM/Movie`, `DCIM/Movie/Parking`, `DCIM/Movie/RO`, and `DCIM/Photo` with three-channel F/I/R and PF/PI/PR suffixes. The first completed scan (84 files) was owner-labeled normal-bitrate driving and low-bitrate parking. The second completed scan (36 files) was owner-labeled high-bitrate driving and 1-fps Night Vision time-lapse parking. These settings can coexist on a card; sample clips by folder/channel and do not extrapolate a setting to all files.

The second submission's `RO` parking clips have 30-fps playback, with two recording starts 30 minutes apart and one 60-second clip per channel; this is consistent with 1-fps capture over 30 minutes. The owner reports no audio on those clips, but the sanitized scan does not include audio-track presence. Do **not** label all RO clips as impact events or all RO clips as time lapse: use model-specific media timing/audio context or a confirmed setting. The profile folder mapping stays protected/neutral pending that classification fix.

No raw user frames or footage are distributed in this repository. App OCR is local and sends neither frames nor OSD text in feedback.
11 changes: 5 additions & 6 deletions docs/releases/latest.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Dashcam Offloader 0.1.28
# Dashcam Offloader 0.1.29

- Prevent a card-learning submission before a complete, nonempty file scan, or after a card rescan changes the reviewed data.
- Refuse learning submissions with unreadable video bitrate metadata rather than silently sending only folder counts. Show an actionable warning in the review window.
- Show every measured video group in the review window; bitrate ranges are samples by folder, recording mode, and camera channel, not one setting assumed for the entire card.
- Scanning remains read-only. Existing footage can be rescanned without formatting the card.
- An incomplete or unreadable learning scan must be corrected before submission; it is not reported as a successful camera test.
- Identify VIOFO T330W by the exact `VIOFO T330W` model stamp in a front-channel video frame during ambiguous VIOFO scans.
- Include an experimental T330W profile for F/I/R and PF/PI/PR channel mapping and the card's Movie, Parking, RO, and Photo folders. Shared VIOFO folder layouts alone remain insufficient for exact identification; T330W RO/PF clips no longer become impact events solely from their location.
- List T330 and T330W as a family with differing rear-camera variants; the non-W model stamp has not yet been validated.
- Scanning and OCR remain local and read-only; video frames are not uploaded with scan feedback.
1 change: 1 addition & 0 deletions docs/supported-cameras.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ which applies.
- Botslab G980H - profile drafted from an app learning submission and real 4CH card sample. Detected from `MISC/G980HMCN5291.TXT`; unknown `360CARDVR` cards should not be assigned this model without exact marker evidence.
- Vueroid S1 4K Infinite - base model profile with 1CH, 2CH, and 3CH variants. One real 3CH sample card inspected.
- Vueroid S1 QHD Infinite - exact config-metadata detection with 1CH, 2CH, and 3CH variants learned from four app submissions; firmware 1.0.4 30 fps 3CH, 60 fps front-only, 5 fps time-lapse parking, and 30 fps parking-impact behavior observed.
- VIOFO T330W - experimental 3CH profile from two app learning submissions and three owner-provided video frames showing the exact `VIOFO T330W` bottom-center stamp. The shared VIOFO folder/filename layout does not identify the model by itself; local OCR can identify it when the configurable stamp is enabled. T330 is the same main-camera family with a different rear-camera variant, but the non-W stamp has not been validated. See `docs/card-profiles/viofo-t330w.md`.
- VIOFO A329S - base model profile. One real 3CH sample card inspected.
- VIOFO A329T - related telephoto model profile drafted from official VIOFO product references and user-provided filename evidence. No real card inspected yet.
- BlackVue Elite 9 - base model profile. One real 2CH sample card inspected.
Expand Down
89 changes: 89 additions & 0 deletions profiles/viofo-t330w.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
id: viofo-t330w
manufacturer: VIOFO
model: T330W
status: experimental
source_notes:
- docs/card-profiles/viofo-t330w.md
detection:
high_confidence:
- method: osd_ocr
source: front_channel_video
strip: bottom_10pct
matches: "VIOFO T330W"
notes: >
Owner-provided frames show the exact model stamp near the bottom center
of front, rear, and interior footage. Do not infer this model from the
shared VIOFO card layout when the camera model stamp is disabled.
supporting:
- folder: DCIM/Movie
- folder: DCIM/Movie/Parking
- folder: DCIM/Movie/RO
- folder: DCIM/Photo
folders:
- path: DCIM/Movie
mode: continuous
validation: app_submission_sampled
- path: DCIM/Movie/Parking
mode: parking
validation: app_submission_sampled
- path: DCIM/Movie/RO
mode: protected
validation: app_submission_sampled
notes: >
May contain time-lapse parking clips, as confirmed for the owner's
1-fps Night Vision test; do not infer impact detection from RO alone.
- path: DCIM/Photo
mode: still_image
validation: app_submission_sampled
filename_patterns:
- pattern: "^(\\d{4})_(\\d{4})_(\\d{6})_(\\d+)(F|I|R|PF|PI|PR)\\.MP4$"
applies_to:
- DCIM/Movie
- DCIM/Movie/Parking
- DCIM/Movie/RO
case_sensitive: true
timestamp:
year_group: 1
month_day_group: 2
time_group: 3
format: YYYY_MMDD_HHMMSS
sequence_group: 4
channel_group: 5
- pattern: "^(\\d{4})_(\\d{4})_(\\d{6})_(\\d+)(F|I|R|PF|PI|PR)\\.JPG$"
applies_to:
- DCIM/Photo
case_sensitive: true
timestamp:
year_group: 1
month_day_group: 2
time_group: 3
format: YYYY_MMDD_HHMMSS
channels:
F: front
I: interior
R: rear
PF: parking_front
PI: parking_interior
PR: parking_rear
grouping:
keys:
- year
- month_day
- time
expected_sets:
normal:
- F
- I
- R
parking:
- PF
- PI
- PR
exclude:
- .Trashes/**
- .fseventsd/**
- .Spotlight-V100/**
- "._*"
- format.txt
- DCIM/.diskdb
- DCIM/Movie/.dashcamexport/**
4 changes: 2 additions & 2 deletions scripts/build-macos-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ cat > "$CONTENTS_DIR/Info.plist" <<PLIST
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.28</string>
<string>0.1.29</string>
<key>CFBundleVersion</key>
<string>28</string>
<string>29</string>
<key>DashcamOffloaderBuildCommit</key>
<string>$BUILD_COMMIT</string>
<key>LSMinimumSystemVersion</key>
Expand Down
Loading